-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
163 lines (138 loc) · 4.87 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
const settings = {
positionClipValue: 1.5,
set posX(value) {
value = (value < 0 ? -1 : 1) * Math.min(this.positionClipValue, Math.abs(value));
return document.getElementById("pos-x").value = value;
},
get posX() {
return eval(document.getElementById("pos-x").value);
},
set posY(value) {
value = (value < 0 ? -1 : 1) * Math.min(this.positionClipValue, Math.abs(value));
return document.getElementById("pos-y").value = value;
},
get posY() {
return eval(document.getElementById("pos-y").value);
},
set zoom(value) {
// Prevent user from zooming in too far, i.e., everything is one big blob of color.
if (this.zoomFunction(value) >= 1318815734) {
return;
}
return document.getElementById("zoom").value = Math.max(1, value);
},
get zoom() {
return eval(document.getElementById("zoom").value);
},
get zoomFunction() {
const checkedInput = () => document.querySelector('input[name="zoom-function"]:checked');
const userFunction = checkedInput().value;
try {
// Try executing the function to catch any errors
eval(userFunction)(0);
return eval(userFunction);
} catch (e) {
if (userFunction.length > 0) {
alert(`Invalid zoom function: "${checkedInput().value}"\nError: ${e.message}`);
// Set a different (random) zoom function and return it if possible
document.querySelector('input[name="zoom-function"]:not(:checked)').checked = true;
return eval(checkedInput().value);
} else {
// Return linear function if user hasn't supplied any function, yet.
return x => x;
}
}
},
get colorFactors() {
function value(el) {
return Number.parseInt(el.max) - Number.parseInt(el.value) + Number.parseInt(el.min);
}
const redFactorEl = document.getElementById("red-factor");
const greenFactorEl = document.getElementById("green-factor");
const blueFactorEl = document.getElementById("blue-factor");
return [value(redFactorEl), value(greenFactorEl), value(blueFactorEl)];
},
get backgroundColor() {
function value(id) {
return Math.min(Math.max(0, Number.parseFloat(document.getElementById(id).value)), 1);
}
return [
value("bg-red"),
value("bg-green"),
value("bg-blue"),
1
]
},
}
function setCustomZoomFunction(value) {
document.getElementById('custom-zoom-func-radio-btn').value = `x => ${value}`;
update();
}
function setCanvasSize(canvas) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function drawFrame() {
const mandelbrotCanvas = document.getElementById("mandelbrot-canvas");
setCanvasSize(mandelbrotCanvas);
render.setOutput([window.innerWidth, window.innerHeight]);
render(window.innerWidth, window.innerHeight, settings.posX, settings.posY, settings.zoomFunction(settings.zoom),
settings.backgroundColor, settings.colorFactors);
mandelbrotCanvas.replaceWith(render.canvas);
render.canvas.id = "mandelbrot-canvas";
}
let lastFrameTime = 0;
function update() {
const diff = performance.now() - lastFrameTime;
const fps = 60;
if (diff < 1000 / fps) {
return;
}
lastFrameTime = performance.now();
requestAnimationFrame(drawFrame);
}
const gpu = new GPU();
const render = gpu.createKernel(function (width, height, posX, posY, zoom, bgColor, colorFactors) {
const maxIter = this.constants.iterations;
const px = this.thread.x;
const py = this.thread.y;
this.color(bgColor[0], bgColor[1], bgColor[2], bgColor[3]);
const x0 = (px - width / 2) / zoom + posX;
const y0 = (py - height / 2) / zoom - posY;
let x = 0.0;
let y = 0.0;
for (let i = 0; i < maxIter; i++) {
const xTemp = (x * x) - (y * y) + x0;
y = 2 * x * y + y0;
x = xTemp;
if (xTemp > maxIter) {
const tNormal = i / maxIter;
const red = tNormal * (Math.sin(i) + (maxIter / colorFactors[0]) + 1) / 2;
const green = tNormal * (Math.sin(i) + (maxIter / colorFactors[1]) + 1) / 2;
const blue = tNormal * (Math.sin(i) + (maxIter / colorFactors[2]) + 1) / 2;
this.color(red, green, blue, 1);
break;
}
}
}, {
constants: {iterations: 200}
}).setDynamicOutput(true)
.setGraphical(true);
window.addEventListener("resize", update);
update();
function toggleShowSideBarBtn() {
const closed = document.getElementById("show-sidebar-btn-closed");
const open = document.getElementById("show-sidebar-btn-open");
const tmp = open.style.display;
const isOpen = open.style.display === 'none';
open.style.display = closed.style.display;
closed.style.display = tmp;
const settings = document.getElementById("settings");
const children = Array.from(settings.children).filter(el => !el.classList.contains("show-sidebar-btn"));
children.forEach(el => el.style.display = isOpen ? null : 'none');
if (!isOpen) {
settings.classList.add("closed");
} else {
settings.classList.remove("closed");
}
}