Skip to content

Commit 1eb458a

Browse files
committed
- add more color schemes and fractal types
1 parent 2573f9e commit 1eb458a

File tree

1 file changed

+190
-37
lines changed

1 file changed

+190
-37
lines changed

tools/fractal_viewer.html

Lines changed: 190 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,37 @@
1717
<canvas id="fractalCanvas" class="w-full rounded-lg shadow-inner mb-4 cursor-move"></canvas>
1818
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
1919
<div><label class="block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" for="fractalType">Fractal
20-
Type:</label><select id="fractalType"
21-
class="w-full p-2 border rounded dark:bg-gray-700 dark:text-white">
22-
<option value="mandelbrot">Mandelbrot Set</option>
23-
<option value="julia">Julia Set</option>
24-
<option value="burningShip">Burning Ship</option>
25-
<option value="mandelbox">Mandelbox</option>
26-
</select></div>
20+
Type:</label>
21+
<select id="fractalType" class="w-full p-2 border rounded dark:bg-gray-700 dark:text-white">
22+
<option value="mandelbrot">Mandelbrot Set</option>
23+
<option value="julia">Julia Set</option>
24+
<option value="burningShip">Burning Ship</option>
25+
<option value="mandelbox">Mandelbox</option>
26+
<option value="sierpinskiCarpet">Sierpinski Carpet</option>
27+
<option value="lyapunov">Lyapunov Fractal</option>
28+
<option value="phoenix">Phoenix Fractal</option>
29+
<option value="mandelbox3DSlice">Mandelbox 3D Slice</option>
30+
<option value="newtonFractal">Newton Fractal</option>
31+
</select>
32+
</div>
2733
<div><label class="block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" for="maxIterations">Max
2834
Iterations: <span id="iterationValue">100</span></label><input type="range" id="maxIterations" min="50"
2935
max="1000" value="100" class="w-full">
3036
</div>
3137
<div><label class="block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" for="colorScheme">Color
32-
Scheme:</label><select id="colorScheme"
33-
class="w-full p-2 border rounded dark:bg-gray-700 dark:text-white">
38+
Scheme:</label><select id="colorScheme" class="w-full p-2 border rounded dark:bg-gray-700 dark:text-white">
3439
<option value="default">Default</option>
3540
<option value="rainbow">Rainbow</option>
3641
<option value="fire">Fire</option>
3742
<option value="ocean">Ocean</option>
3843
<option value="psychedelic">Psychedelic</option>
44+
<option value="pastel">Pastel</option>
45+
<option value="neon">Neon</option>
46+
<option value="grayscale">Grayscale</option>
47+
<option value="autumn">Autumn</option>
48+
<option value="electric">Electric</option>
49+
<option value="cosmic">Cosmic</option>
50+
<option value="vintage">Vintage</option>
3951
</select></div>
4052
<div><label class="block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" for="juliaReal">Julia
4153
Real:</label><input type="number" id="juliaReal" value="-0.7" step="0.1"
@@ -95,20 +107,51 @@
95107
const type = fractalType.value;
96108
const jr = parseFloat(juliaReal.value);
97109
const ji = parseFloat(juliaImag.value);
110+
98111
for (let x = 0; x < width; x++) {
99112
for (let y = 0; y < height; y++) {
100113
const zx = 1.5 * (x - width / 2) / (0.5 * zoomLevel * width) + centerX;
101114
const zy = (y - height / 2) / (0.5 * zoomLevel * height) + centerY;
102-
let i;
103-
if (type === 'julia') i = julia(zx, zy, jr, ji, maxIter); else if (type === 'burningShip') i = burningShip(zx, zy, maxIter); else if (type === 'mandelbox') i = mandelbox(zx, zy, maxIter); else i = mandelbrot(zx, zy, maxIter);
104-
const [r, g, b] = getColor(i, maxIter, scheme);
115+
116+
let value;
117+
switch (type) {
118+
case 'julia':
119+
value = julia(zx, zy, jr, ji, maxIter);
120+
break;
121+
case 'burningShip':
122+
value = burningShip(zx, zy, maxIter);
123+
break;
124+
case 'mandelbox':
125+
value = mandelbox(zx, zy, maxIter);
126+
break;
127+
case 'sierpinskiCarpet':
128+
value = sierpinskiCarpet(zx, zy, maxIter);
129+
break;
130+
case 'lyapunov':
131+
value = lyapunov(zx, zy, maxIter);
132+
break;
133+
case 'phoenix':
134+
value = phoenix(zx, zy, maxIter);
135+
break;
136+
case 'mandelbox3DSlice':
137+
value = mandelbox3DSlice(zx, zy, maxIter);
138+
break;
139+
case 'newtonFractal':
140+
value = newtonFractal(zx, zy, maxIter);
141+
break;
142+
default:
143+
value = mandelbrot(zx, zy, maxIter);
144+
}
145+
146+
const [r, g, b] = getColor(value, maxIter, scheme, type);
105147
const pos = (y * width + x) * 4;
106148
data[pos] = r;
107149
data[pos + 1] = g;
108150
data[pos + 2] = b;
109151
data[pos + 3] = 255;
110152
}
111153
}
154+
112155
ctx.putImageData(imageData, 0, 0);
113156
}
114157

@@ -185,18 +228,140 @@
185228
return c;
186229
}
187230

188-
function getColor(i, maxIter, scheme) {
189-
if (i === maxIter) return [0, 0, 0];
190-
const t = i / maxIter;
231+
function sierpinskiCarpet(x, y, maxIter) {
232+
let iter = 0;
233+
while (iter < maxIter) {
234+
if ((Math.floor(x * 3) % 3 == 1) && (Math.floor(y * 3) % 3 == 1)) {
235+
return iter;
236+
}
237+
x *= 3;
238+
y *= 3;
239+
x -= Math.floor(x);
240+
y -= Math.floor(y);
241+
iter++;
242+
}
243+
return maxIter;
244+
}
245+
246+
function lyapunov(x, y, maxIter) {
247+
const a = 3.7 * x;
248+
const b = 3.7 * y;
249+
let x0 = 0.5;
250+
let sum = 0;
251+
for (let i = 0; i < maxIter; i++) {
252+
const r = i % 2 === 0 ? a : b;
253+
x0 = r * x0 * (1 - x0);
254+
sum += Math.log(Math.abs(r * (1 - 2 * x0)));
255+
}
256+
return sum / maxIter;
257+
}
258+
259+
function phoenix(x, y, maxIter) {
260+
let x1 = x, y1 = y, x2 = 0, y2 = 0;
261+
const p = -0.5, q = 0.0;
262+
for (let i = 0; i < maxIter; i++) {
263+
const xx = x1 * x1 - y1 * y1 + x + p * x2;
264+
const yy = 2 * x1 * y1 + y + q * y2;
265+
if (xx * xx + yy * yy > 4) return i;
266+
x2 = x1;
267+
y2 = y1;
268+
x1 = xx;
269+
y1 = yy;
270+
}
271+
return maxIter;
272+
}
273+
274+
function mandelbox3DSlice(x, y, maxIter) {
275+
const scale = 2;
276+
let zx = x, zy = y, zz = 0;
277+
let c = 0;
278+
for (let i = 0; i < maxIter; i++) {
279+
zx = clamp(zx, -1, 1) * 2 - zx;
280+
zy = clamp(zy, -1, 1) * 2 - zy;
281+
zz = clamp(zz, -1, 1) * 2 - zz;
282+
283+
const r = Math.sqrt(zx*zx + zy*zy + zz*zz);
284+
if (r < 0.5) {
285+
zx *= 4; zy *= 4; zz *= 4;
286+
} else if (r < 1) {
287+
zx /= r*r; zy /= r*r; zz /= r*r;
288+
}
289+
290+
zx = zx * scale + x;
291+
zy = zy * scale + y;
292+
zz = zz * scale;
293+
294+
if (zx*zx + zy*zy + zz*zz > 4) return i;
295+
c++;
296+
}
297+
return c;
298+
}
299+
300+
function clamp(x, min, max) {
301+
return Math.min(Math.max(x, min), max);
302+
}
303+
304+
function newtonFractal(x, y, maxIter) {
305+
let zx = x, zy = y;
306+
for (let i = 0; i < maxIter; i++) {
307+
const zx2 = zx * zx, zy2 = zy * zy;
308+
const zx3 = zx2 * zx - 3 * zx * zy2;
309+
const zy3 = 3 * zx2 * zy - zy2 * zy;
310+
const mag = zx3 * zx3 + zy3 * zy3;
311+
if (mag < 1e-6) return i;
312+
const denom = 3 * (zx2 + zy2);
313+
zx -= (zx3 + 1) / denom;
314+
zy -= zy3 / denom;
315+
}
316+
return maxIter;
317+
}
318+
319+
function getColor(value, maxIter, scheme, type) {
320+
if (type === 'lyapunov') {
321+
// Lyapunov fractal uses a different coloring scheme
322+
const hue = (value + 5) / 10; // Adjust this range as needed
323+
return hsvToRgb(hue, 1, 1);
324+
}
325+
326+
if (value === maxIter) return [0, 0, 0];
327+
328+
const t = value / maxIter;
191329
switch (scheme) {
192-
case'rainbow':
330+
case 'rainbow':
193331
return hsvToRgb(t, 1, 1);
194-
case'fire':
332+
case 'fire':
195333
return hsvToRgb(t / 3, 1, Math.min(1, t * 2));
196-
case'ocean':
334+
case 'ocean':
197335
return hsvToRgb(0.6 + t / 3, 1, Math.min(1, t * 2));
198-
case'psychedelic':
336+
case 'psychedelic':
199337
return hsvToRgb(Math.sin(t * Math.PI), 1, 1);
338+
case 'pastel':
339+
return hsvToRgb(t, 0.5, 1);
340+
case 'neon':
341+
return hsvToRgb(t, 1, t < 0.5 ? 0.5 + t : 1);
342+
case 'grayscale':
343+
const gray = Math.floor(t * 255);
344+
return [gray, gray, gray];
345+
case 'autumn':
346+
return [
347+
Math.floor(255 * t),
348+
Math.floor(128 * Math.sin(Math.PI * t)),
349+
Math.floor(64 * (1 - t))
350+
];
351+
case 'electric':
352+
return hsvToRgb(0.6 + 0.4 * t, 1, t < 0.5 ? 2 * t : 1);
353+
case 'cosmic':
354+
return [
355+
Math.floor(128 * (1 + Math.sin(2 * Math.PI * t))),
356+
Math.floor(128 * (1 + Math.sin(2 * Math.PI * t + 2 * Math.PI / 3))),
357+
Math.floor(128 * (1 + Math.sin(2 * Math.PI * t + 4 * Math.PI / 3)))
358+
];
359+
case 'vintage':
360+
return [
361+
Math.floor(255 * (0.5 + 0.5 * Math.sin(Math.PI * t))),
362+
Math.floor(255 * (0.5 + 0.5 * Math.sin(Math.PI * t + Math.PI / 2))),
363+
Math.floor(255 * (0.5 + 0.5 * Math.sin(Math.PI * t + Math.PI)))
364+
];
200365
default:
201366
return hsvToRgb(t, 1, Math.sqrt(t));
202367
}
@@ -210,24 +375,12 @@
210375
const q = v * (1 - f * s);
211376
const t = v * (1 - (1 - f) * s);
212377
switch (i % 6) {
213-
case 0:
214-
r = v, g = t, b = p;
215-
break;
216-
case 1:
217-
r = q, g = v, b = p;
218-
break;
219-
case 2:
220-
r = p, g = v, b = t;
221-
break;
222-
case 3:
223-
r = p, g = q, b = v;
224-
break;
225-
case 4:
226-
r = t, g = p, b = v;
227-
break;
228-
case 5:
229-
r = v, g = p, b = q;
230-
break;
378+
case 0: r = v, g = t, b = p; break;
379+
case 1: r = q, g = v, b = p; break;
380+
case 2: r = p, g = v, b = t; break;
381+
case 3: r = p, g = q, b = v; break;
382+
case 4: r = t, g = p, b = v; break;
383+
case 5: r = v, g = p, b = q; break;
231384
}
232385
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
233386
}

0 commit comments

Comments
 (0)