|
17 | 17 | <canvas id="fractalCanvas" class="w-full rounded-lg shadow-inner mb-4 cursor-move"></canvas> |
18 | 18 | <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> |
19 | 19 | <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> |
27 | 33 | <div><label class="block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" for="maxIterations">Max |
28 | 34 | Iterations: <span id="iterationValue">100</span></label><input type="range" id="maxIterations" min="50" |
29 | 35 | max="1000" value="100" class="w-full"> |
30 | 36 | </div> |
31 | 37 | <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"> |
34 | 39 | <option value="default">Default</option> |
35 | 40 | <option value="rainbow">Rainbow</option> |
36 | 41 | <option value="fire">Fire</option> |
37 | 42 | <option value="ocean">Ocean</option> |
38 | 43 | <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> |
39 | 51 | </select></div> |
40 | 52 | <div><label class="block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" for="juliaReal">Julia |
41 | 53 | Real:</label><input type="number" id="juliaReal" value="-0.7" step="0.1" |
|
95 | 107 | const type = fractalType.value; |
96 | 108 | const jr = parseFloat(juliaReal.value); |
97 | 109 | const ji = parseFloat(juliaImag.value); |
| 110 | + |
98 | 111 | for (let x = 0; x < width; x++) { |
99 | 112 | for (let y = 0; y < height; y++) { |
100 | 113 | const zx = 1.5 * (x - width / 2) / (0.5 * zoomLevel * width) + centerX; |
101 | 114 | 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); |
105 | 147 | const pos = (y * width + x) * 4; |
106 | 148 | data[pos] = r; |
107 | 149 | data[pos + 1] = g; |
108 | 150 | data[pos + 2] = b; |
109 | 151 | data[pos + 3] = 255; |
110 | 152 | } |
111 | 153 | } |
| 154 | + |
112 | 155 | ctx.putImageData(imageData, 0, 0); |
113 | 156 | } |
114 | 157 |
|
|
185 | 228 | return c; |
186 | 229 | } |
187 | 230 |
|
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; |
191 | 329 | switch (scheme) { |
192 | | - case'rainbow': |
| 330 | + case 'rainbow': |
193 | 331 | return hsvToRgb(t, 1, 1); |
194 | | - case'fire': |
| 332 | + case 'fire': |
195 | 333 | return hsvToRgb(t / 3, 1, Math.min(1, t * 2)); |
196 | | - case'ocean': |
| 334 | + case 'ocean': |
197 | 335 | return hsvToRgb(0.6 + t / 3, 1, Math.min(1, t * 2)); |
198 | | - case'psychedelic': |
| 336 | + case 'psychedelic': |
199 | 337 | 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 | + ]; |
200 | 365 | default: |
201 | 366 | return hsvToRgb(t, 1, Math.sqrt(t)); |
202 | 367 | } |
|
210 | 375 | const q = v * (1 - f * s); |
211 | 376 | const t = v * (1 - (1 - f) * s); |
212 | 377 | 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; |
231 | 384 | } |
232 | 385 | return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; |
233 | 386 | } |
|
0 commit comments