Spent the whole day watching math videos on YouTube. This seemed the logical next step..
A Pen by Andreas Borgen on CodePen.
| <canvas></canvas> |
| console.clear(); | |
| /* | |
| c = a + bi | |
| */ | |
| class ComplexNumber { | |
| constructor(a, b) { | |
| this.a = a; | |
| this.b = b; | |
| } | |
| squared() { | |
| // (a + bi)*(a + bi) = | |
| // a² + a*bi + a*bi + b²i² = | |
| // a² + 2abi - b² = | |
| // (a² - b²) + 2abi | |
| const a = this.a, | |
| b = this.b, | |
| newA = a*a - b*b, | |
| newB = 2*a*b; | |
| return new ComplexNumber(newA, newB); | |
| } | |
| add(c) { | |
| // (a + bi) + (A + Bi) = | |
| // (a + A) + (b + B)i | |
| return new ComplexNumber(this.a + c.a, this.b + c.b); | |
| } | |
| } | |
| /* Usage: | |
| var cb = new CanvasPixelBuffer(myCanvas); | |
| cb.setPixel(10,20, 100); | |
| cb.setPixel(11,21, 100); | |
| cb.render(); | |
| */ | |
| class CanvasPixelBuffer { | |
| constructor(canvas, w, h) { | |
| this.w = canvas.width = (w || canvas.width); | |
| this.h = canvas.height = (h || canvas.height); | |
| this.targetContext = canvas.getContext('2d'); | |
| this.targetData = this.targetContext.getImageData(0,0, this.w,this.h); | |
| } | |
| //http://stackoverflow.com/questions/13917139/fastest-way-to-iterate-pixels-in-a-canvas-and-copy-some-of-them-in-another-one | |
| setPixel(x, y, rgb) { | |
| const index = (y * this.w + x) * 4, //Index of the current pixel | |
| data = this.targetData.data; | |
| data[index] = rgb[0]; //r | |
| data[index + 1] = rgb[1]; //g | |
| data[index + 2] = rgb[2]; //b | |
| data[index + 3] = (rgb.length > 3) ? rgb[3] : 255; //a | |
| } | |
| render() { | |
| this.targetContext.putImageData(this.targetData, 0,0); | |
| } | |
| } | |
| function mandelbrot(iterations, area, viewportSize) { | |
| function testNumber(a, b) { | |
| //Create the main iterated Mandelbrot set function f(z): | |
| const c = new ComplexNumber(a, b); | |
| function f(z) { | |
| return z.squared().add(c); | |
| } | |
| let n = 0, z = c; | |
| for(let i = 1; i <= iterations; i++) { | |
| z = f(z); | |
| if( (Math.abs(z.a) > 2) || (Math.abs(z.b) > 2) ) { | |
| n = i; | |
| break; | |
| } | |
| } | |
| return n; | |
| } | |
| const viewW = viewportSize[0], | |
| viewH = viewportSize[1], | |
| zoom = area.size[0] / viewW, | |
| offsetA = area.topLeft[0], | |
| offsetB = area.topLeft[1], | |
| set = []; | |
| let a, b, n; | |
| for(let y = 0; y < viewH; y++) { | |
| const row = [] | |
| set.push(row); | |
| for(let x = 0; x < viewW; x++) { | |
| a = (x * zoom) + offsetA; | |
| b = (y * zoom) + offsetB; | |
| n = testNumber(a, b); | |
| row.push(n); | |
| } | |
| } | |
| return set; | |
| } | |
| (function() { | |
| "use strict"; | |
| const maxIters = 30, | |
| viewBox = { | |
| topLeft: [-2.3, -1.201], | |
| size: [3.5, null], | |
| }; | |
| const w = 800, | |
| h = 550, | |
| colors = ['#ffff00','#cbff00','#8dff00','#00ff00','#40ff78','#43ffbd','#00ffff','#55b6ff','#506dff','#0000ff','#8a00ff','#c900ff','#ff00ff','#ff00ae','#ff0060','#ff0000','#ff7f00','#ffc200'] | |
| .map(hex => hex.slice(1).match(/(..)/g).map(x => parseInt(x, 16))); | |
| const canvas = document.querySelector('canvas'), | |
| buffer = new CanvasPixelBuffer(canvas, w, h); | |
| const set = mandelbrot(maxIters, viewBox, [w, h]); | |
| let n; | |
| for(let y = 0; y < h; y++) { | |
| for(let x = 0; x < w; x++) { | |
| n = set[y][x]; | |
| buffer.setPixel(x, y, (n === 0) ? [0, 0, 0] : colors[(n - 1) % colors.length]); | |
| } | |
| } | |
| buffer.render(); | |
| })(); |
Spent the whole day watching math videos on YouTube. This seemed the logical next step..
A Pen by Andreas Borgen on CodePen.