diff --git a/combo.js b/combo.js index 04132f7..9a5ecb9 100644 --- a/combo.js +++ b/combo.js @@ -1,11 +1,20 @@ +importScripts('fractal.js'); + class ComboFractal extends Mandelbrot { constructor() { super(); + // TODO(mime): move out of web worker document.getElementById('julia-map').innerHTML = ''; this.julia = new Julia('julia-canvas'); } + setCanvas(canvas, height) { + super.setCanvas(canvas, height); + + postMessage(this.variables); + } + setOptionsAndDraw(options, opt_mouseX, opt_mouseY) { super.setOptionsAndDraw(options); @@ -25,6 +34,20 @@ class ComboFractal extends Mandelbrot { } dispose() { + // TODO(mime): hook up to webworker document.getElementById('julia-map').innerHTML = ''; } } + + +const instance = new ComboFractal(); +onmessage = (e) => { + switch (e.data.msg) { + case 'init': + instance.setCanvas(e.data.canvas, e.data.height); + break; + default: + instance.setOptionsAndDraw(e.data.options, e.data.mouseX, e.data.mouseY); + break; + } +}; diff --git a/fractal.js b/fractal.js index eb62df5..61e1517 100644 --- a/fractal.js +++ b/fractal.js @@ -1,12 +1,15 @@ class Fractal { - constructor(canvasId) { + constructor() { this.variables = {}; + this.variableLocations = {}; + } - this.canvas = document.getElementById(canvasId || 'canvas'); - this.canvas.width = this.canvas.height = this.canvas.clientHeight; + setCanvas(canvas, height) { + this.canvas = canvas; + this.canvas.width = this.canvas.height = height; this.gl = this.canvas.getContext('webgl'); - this.gl.viewport(0, 0, this.canvas.clientHeight, this.canvas.clientHeight); + this.gl.viewport(0, 0, height, height); this.glDrawArraysMode = this.gl.TRIANGLE_FAN; @@ -46,13 +49,14 @@ class Fractal { } dispose() { - + } setOptionsAndDraw(options, opt_mouseX, opt_mouseY) { for (const key in options) { this.variables[key].value = options[key]; } + postMessage(this.variables); this.draw(); } @@ -64,7 +68,7 @@ class Fractal { for (const key in this.variables) { const variable = this.variables[key]; - this.gl['uniform' + variable.type](variable.location, variable.value); + this.gl['uniform' + variable.type](this.variableLocations[key], variable.value); } this.gl.drawArrays(this.glDrawArraysMode, 0, 4); } @@ -92,7 +96,7 @@ class Fractal { for (const key in this.variables) { const variable = this.variables[key]; - variable.location = this.gl.getUniformLocation(prog, key); + this.variableLocations[key] = this.gl.getUniformLocation(prog, key); } } diff --git a/fractals.html b/fractals.html index a69a95c..304c23e 100644 --- a/fractals.html +++ b/fractals.html @@ -58,10 +58,6 @@

inspiration

- - - - diff --git a/julia.js b/julia.js index 345a32c..56bb715 100644 --- a/julia.js +++ b/julia.js @@ -1,8 +1,10 @@ // adapted and greatly modified from http://universefactory.net/test/julia/ +importScripts('fractal.js'); + class Julia extends Fractal { - constructor(canvasId) { - super(canvasId); + constructor() { + super(); this.variables = { antiAlias: { type: '1i', value: 1 }, @@ -15,9 +17,15 @@ class Julia extends Fractal { zoom: { type: '1f', value: 1.5 }, }; this.buffer = [-1, -1, 1, -1, 1, 1, -1, 1]; + } + + setCanvas(canvas, height) { + super.setCanvas(canvas, height); this.buildProgram(this.vertexShader, this.fragmentShader); this.assignAttribOffsets(0, 2, { p: 0 }); + + postMessage(this.variables); } get vertexShader() { @@ -82,3 +90,15 @@ class Julia extends Fractal { ` } } + +const instance = new Julia(); +onmessage = (e) => { + switch (e.data.msg) { + case 'init': + instance.setCanvas(e.data.canvas, e.data.height); + break; + default: + instance.setOptionsAndDraw(e.data.options); + break; + } +}; \ No newline at end of file diff --git a/mandelbrot.js b/mandelbrot.js index 7a40254..3947c4b 100644 --- a/mandelbrot.js +++ b/mandelbrot.js @@ -3,12 +3,11 @@ // http://hvidtfeldts.net/WebGL-DP/webgl.html (code is out-of-date but works if you tweak it) // http://hvidtfeldts.net/WebGL/webgl.html -class Mandelbrot extends Fractal { - constructor(canvasId) { - super(canvasId); +importScripts('fractal.js'); - const w = this.canvas.width * 0.5; - const h = this.canvas.height * 0.5; +class Mandelbrot extends Fractal { + constructor() { + super(); this.variables = { antiAlias: { type: '1i', value: 1 }, @@ -18,7 +17,7 @@ class Mandelbrot extends Fractal { iterations: { type: '1i', value: 128 }, offsetX: { type: '1f', value: 0.0 }, offsetY: { type: '1f', value: 0.0 }, - pixelSize: { type: '2fv', value: [1.0 / w, 1.0 / h] }, + pixelSize: { type: '2fv', value: [1.0, 1.0] }, time: { type: '1f', value: Date.now() / 1000 }, zoom: { type: '1f', value: 1.5 }, }; @@ -29,11 +28,21 @@ class Mandelbrot extends Fractal { 1.0, -1.0, 0.0, -1.0, -1.0, 0.0, ]; + } + + setCanvas(canvas, height) { + super.setCanvas(canvas, height); + + const w = height * 0.5; + const h = height * 0.5; + this.variables.pixelSize = { type: '2fv', value: [1.0 / w, 1.0 / h] }; this.glDrawArraysMode = this.gl.TRIANGLE_STRIP; this.buildProgram(this.vertexShader, this.doublePrecisionMath + this.fragmentShader); this.assignAttribOffsets(0, 3, { position: 0 }); + + postMessage(this.variables); } preDraw() { @@ -192,3 +201,15 @@ class Mandelbrot extends Fractal { `; } } + +const instance = new Mandelbrot(); +onmessage = (e) => { + switch (e.data.msg) { + case 'init': + instance.setCanvas(e.data.canvas, e.data.height); + break; + default: + instance.setOptionsAndDraw(e.data.options); + break; + } +}; diff --git a/ui.js b/ui.js index 3c3e166..9bc78d5 100644 --- a/ui.js +++ b/ui.js @@ -26,14 +26,19 @@ class FractalUI { this.altPressed = false; this.ctrlPressed = false; - this.fractalOptions = [Julia, Mandelbrot, ComboFractal]; - this.currentFractalIndex = 0; - this.currentFractal = new this.fractalOptions[this.currentFractalIndex]; + this.fractalOptions = ['julia', 'mandelbrot', 'combo']; + this.currentFractalIndex = -1; + this.domCanvas = null; + this.offscreenCanvas = null; + this.currentFractalWorker = null; + this.currentVariables = {}; + + this.changeFractal(); this.bindEventListeners(); - this.currentFractal.setOptionsAndDraw({ - zoom: 1.5, + this.currentFractalWorker.postMessage({ + options: { zoom: 1.5 }, }); setTimeout(() => { @@ -42,11 +47,25 @@ class FractalUI { } changeFractal() { - this.currentFractal.dispose(); + this.currentFractalWorker && this.currentFractalWorker.terminate(); + + document.getElementById('container').innerHTML = ''; + this.domCanvas = document.getElementById('canvas'); + this.offscreenCanvas = this.domCanvas.transferControlToOffscreen(); - document.getElementById('container').innerHtml = ''; this.currentFractalIndex = (this.currentFractalIndex + 1) % this.fractalOptions.length; - this.currentFractal = new this.fractalOptions[this.currentFractalIndex]; + this.currentFractalWorker = new Worker(`${this.fractalOptions[this.currentFractalIndex]}.js`); + this.currentFractalWorker.postMessage( + { + msg: 'init', + canvas: this.offscreenCanvas, + height: this.domCanvas.clientHeight, + }, + [this.offscreenCanvas] + ); + this.currentFractalWorker.onmessage = (e) => { + this.currentVariables = e.data; + }; } bindEventListeners() { @@ -70,7 +89,7 @@ class FractalUI { } getVarValue(variable) { - return this.currentFractal.variables[variable].value; + return this.currentVariables[variable].value; } onMouseMove(evt) { @@ -88,7 +107,7 @@ class FractalUI { this.mouseX = canvasCenterX; this.mouseY = canvasCenterY; - const isComboFractal = this.currentFractal instanceof ComboFractal; + const isComboFractal = this.fractalOptions[this.currentFractalIndex] === 'combo'; const zoom = this.getVarValue('zoom'); if (!isComboFractal && !this.isMousePressed) { if (this.ZOOM_LIMIT != this.ZOOM_MAX && Date.now() < this.latestMouseWheelEvent + 1000) { @@ -113,24 +132,27 @@ class FractalUI { const PAN_INTERVAL = zoom * this.PANNING_SPEED * 0.1; offsetX += deltaX * PAN_INTERVAL; offsetY += -1 * deltaY * PAN_INTERVAL; - this.currentFractal.setOptionsAndDraw({ offsetX, offsetY }); + this.currentFractalWorker.postMessage({ options: { offsetX, offsetY } }); } else if (this.altPressed) { blobSize += directionX * 0.01; blobSize = Math.max(Math.min(blobSize, 2.0), 0); - this.currentFractal.setOptionsAndDraw({ blobSize }); + this.currentFractalWorker.postMessage({ options: { blobSize } }); } else if (this.ctrlPressed) { colorControl += directionX * 0.1; colorControl = Math.max(Math.min(colorControl, 1000.0), 0); - this.currentFractal.setOptionsAndDraw({ colorControl }); + this.currentFractalWorker.postMessage({ options: { colorControl } }); } else if (shiftPressed && zoom < 1.0) { this.x += directionX * this.x * zoom * 0.005; // x is more sensitive so dampen it more this.y += directionY * this.y * zoom * 0.01; - this.currentFractal.setOptionsAndDraw({ center: [this.x, this.y] }); + this.currentFractalWorker.postMessage({ options: { center: [this.x, this.y] } }); } else if (!shiftPressed && (zoom >= this.ZOOM_LIMIT || isComboFractal)) { this.x = (canvasCenterX * this.RETINA_RATIO / canvas.width * 2 - 1) / this.MORPHING_SPEED; this.y = (1 - canvasCenterY * this.RETINA_RATIO / canvas.height * 2) / this.MORPHING_SPEED; - this.currentFractal.setOptionsAndDraw({ center: [this.x, this.y] }, - this.mouseX * this.RETINA_RATIO, this.mouseY * this.RETINA_RATIO); + this.currentFractalWorker.postMessage({ + options: { center: [this.x, this.y] }, + mouseX: this.mouseX * this.RETINA_RATIO, + mouseY: this.mouseY * this.RETINA_RATIO + }); } }); } @@ -149,12 +171,12 @@ class FractalUI { if (this.altPressed) { blobSize += -1 * evt.deltaY * 0.001; blobSize = Math.max(Math.min(blobSize, 2.0), 0); - this.currentFractal.setOptionsAndDraw({ blobSize }); + this.currentFractalWorker.postMessage({ options: { blobSize } }); return; } else if (this.ctrlPressed) { colorControl += evt.deltaY * 0.1; colorControl = Math.max(Math.min(colorControl, 1000.0), 0); - this.currentFractal.setOptionsAndDraw({ colorControl }); + this.currentFractalWorker.postMessage({ options: { colorControl } }); return; } @@ -184,12 +206,14 @@ class FractalUI { antiAlias = 2; } - this.currentFractal.setOptionsAndDraw({ - antiAlias, - zoom, - iterations, - offsetX, - offsetY, + this.currentFractalWorker.postMessage({ + options: { + antiAlias, + zoom, + iterations, + offsetX, + offsetY, + } }); }); } @@ -212,19 +236,19 @@ class FractalUI { break; case 'ArrowLeft': offsetX -= PAN_INTERVAL; - this.currentFractal.setOptionsAndDraw({ offsetX }); + this.currentFractalWorker.postMessage({ options: { offsetX } }); break; case 'ArrowRight': offsetX += PAN_INTERVAL; - this.currentFractal.setOptionsAndDraw({ offsetX }); + this.currentFractalWorker.postMessage({ options: { offsetX } }); break; case 'ArrowDown': offsetY -= PAN_INTERVAL; - this.currentFractal.setOptionsAndDraw({ offsetY }); + this.currentFractalWorker.postMessage({ options: { offsetY } }); break; case 'ArrowUp': offsetY += PAN_INTERVAL; - this.currentFractal.setOptionsAndDraw({ offsetY }); + this.currentFractalWorker.postMessage({ options: { offsetY } }); break; case ' ': this.changeFractal();