blug/site/old-projects/fractals.js
2024-08-09 22:33:19 +02:00

264 lines
7.6 KiB
JavaScript

'use strict'
// I made this in school and I had to 'use strict'.
// I no longer use JS.
class FractalTree {
constructor(id, iterations, mod) {
this.app = document.getElementById(id);
console.debug(this.app);
this.canvas = this.app.getElementsByTagName("canvas")[0];
this.canvas.width = 512;
this.canvas.height = 448;
this.ctx = this.canvas.getContext("2d");
this.iterations = iterations;
this.mod = mod;
this.angle = Math.PI * 0.2;
this.update = true;
this.palette = ["#430", "#440", "#450", "#460", "#470", "#480", "#080"];
this.canvas.addEventListener("mousemove", this.mouseMove.bind(this));
this.canvas.addEventListener("mousedown", this.click.bind(this));
}
static vector(l, angle) {
return {
x: Math.cos(angle) * l,
y: Math.sin(angle) * l
};
}
tree(x, y, length, i, angle) {
if (i==0) { return; }
let dir = FractalTree.vector(length * this.mod, angle + this.angle);
this.ctx.beginPath();
this.ctx.moveTo(x, y);
this.ctx.lineWidth = i;
this.ctx.strokeStyle = this.palette[this.iterations-i];
this.ctx.lineTo(x + dir.x, y - dir.y);
this.ctx.stroke();
this.tree(x + dir.x, y - dir.y, length*this.mod, i-1, angle - this.angle);
this.tree(x + dir.x, y - dir.y, length*this.mod, i-1, angle + this.angle);
}
render() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.tree(256, this.canvas.height-64, 128, this.iterations, Math.PI/2 - this.angle);
}
mouseMove(event) {
if (this.update) {
let x = event.clientX - this.canvas.getBoundingClientRect().left;
this.angle = x * Math.PI / this.canvas.width;
this.render();
}
}
click(event) {
this.update = !this.update;
if (this.update) {
let x = event.clientX - this.canvas.getBoundingClientRect().left;
this.angle = x * Math.PI / this.canvas.width;
this.render();
}
}
setIter(i) {
this.iterations = i;
this.render();
}
setMod(m) {
this.mod = m;
this.render();
}
}
class Mandelbrot {
constructor(id, iter=255, width=512, height=512, minX=-2, minY=-1.5, maxX=1, maxY=1.5) {
this.app = document.getElementById(id);
this.canvas = this.app.getElementsByTagName("canvas")[0];
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext("2d");
this.renderBtn = this.app.getElementsByClassName("controlbar")[0].getElementsByTagName("button")[0];
this.init(minX, minY, maxX, maxY);
this.defaultZoom = [minX, minY, maxX, maxY];
this.iter = iter;
this.palette = [16, 32, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248];
this.canvas.addEventListener("mousedown", this.zoom.bind(this));
}
zoom(event) {
//zoom function
let zoomf = 8;
let x = this.reScaleX(event.clientX - this.canvas.getBoundingClientRect().left);
let y = this.reScaleY(event.clientY - this.canvas.getBoundingClientRect().top);
let dx = (this.maxX - this.minX)/zoomf;
let dy = (this.maxY - this.minY)/zoomf;
this.init(x - dx, y - dy, x + dx, y + dy);
this.render();
}
reset() {
this.init(this.defaultZoom[0], this.defaultZoom[1], this.defaultZoom[2], this.defaultZoom[3]);
this.render();
}
setIter(x) {
this.iter = x;
this.render();
}
init(minX, minY, maxX, maxY) {
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
this.scaleX = (maxX - minX) / this.canvas.width;
this.scaleY = (maxY - minY) / this.canvas.height;
}
reScaleX(x) {
return x * this.scaleX + this.minX;
}
reScaleY(y) {
return y * this.scaleY + this.minY;
}
point(x0, y0) {
let x = 0, y = 0;
let i = 0;
let x2 = 0, y2 = 0;
while (x2 + y2 <= 4 && i < this.iter) {
y = 2*x*y + y0;
x = x2 - y2 + x0;
x2 = x*x;
y2 = y*y;
i++;
}
return i;
}
render() {
let start = new Date();
let result = this.ctx.getImageData(0,0, this.canvas.width, this.canvas.height);
let px = 0;
let yp;
for (let yPos = 0; yPos < this.canvas.height; yPos++) {
yp = this.reScaleY(yPos);
for (let xPos = 0; xPos < this.canvas.width; xPos++) {
let i = this.point(this.reScaleX(xPos), yp);
if (i != this.iter) {
let col = this.palette[Math.min(i, this.palette.length-1)]
result.data[px] = 0;
result.data[px+1] = col;
result.data[px+2] = col;
} else {
result.data[px] = 0;
result.data[px+1] = 0;
result.data[px+2] = 0;
}
result.data[px+3] = 255;
px += 4;
}
}
this.ctx.putImageData(result, 0, 0);
this.renderBtn.textContent = `Reset ${new Date() - start}ms`;
}
}
class Multibrot extends Mandelbrot {
constructor(id, power, iter=255, width=512, height=512, minX=-2, minY=-2, maxX=2, maxY=2) {
super(id, iter, width, height, minX, minY, maxX, maxY);
this.power = power;
}
point(x0, y0) {
let x = 0, y = 0;
let i = 0;
let xtmp, xxyyn, atn;
let n2 = this.power/2;
while (x*x + y*y <= 4 && i < this.iter) {
xxyyn = (x*x+y*y)**(n2);
atn = this.power*Math.atan2(y, x);
xtmp = xxyyn * Math.cos(atn) + x0;
y = xxyyn * Math.sin(atn) + y0;
x = xtmp;
i++;
}
return i;
}
setPower(x) {
this.power = x;
this.render();
}
}
class Julia extends Mandelbrot {
constructor(id, iter=255, width=512, height=512, minX=-2, minY=-2, maxX=2, maxY=2) {
super(id, iter, width, height, minX, minY, maxX, maxY);
this.cx = this.reScaleX(width/2);
this.cy = this.reScaleY(height/3.2);
this.update = true;
this.canvas.removeEventListener("mousedown", this.zoom);
this.canvas.addEventListener("mousedown", this.click.bind(this));
this.canvas.addEventListener("mousemove", this.mouseMove.bind(this));
}
click(event) {
this.update = !this.update;
this.mouseMove(event);
}
zoom() {
return;
}
mouseMove(event) {
if (this.update) {
this.cx = this.reScaleX(event.clientX - this.canvas.getBoundingClientRect().left);
this.cy = this.reScaleY(event.clientY - this.canvas.getBoundingClientRect().top);
this.render();
}
}
point(zx, zy) {
let i = 0, xtmp = 0;
let zx2 = zx*zx, zy2 = zy*zy;
while (zx2 + zy2 < 4 && i < this.iter) {
xtmp = zx2 - zy2;
zy = 2*zx*zy + this.cy;
zx = xtmp + this.cx;
zx2 = zx*zx;
zy2 = zy*zy;
i++;
}
return i;
}
}
let fractalTree = new FractalTree("fractal-tree", 12, 0.75);
let mandelbrot = new Mandelbrot("mandelbrot");
let multibrot = new Multibrot("multibrot", 4);
let juliaSet = new Julia("julia-set", 64, 512, 512);
fractalTree.render();
mandelbrot.render();
juliaSet.render();