mandelbrot_client.js

import * as SVG from 'svg.js';

function showText(msg) {
	let newTextNode = document.createElement('div');
	let textNode = document.createTextNode(msg)
	newTextNode.appendChild(textNode)

	let entry = document.getElementById("entry")
	entry.appendChild(newTextNode)
}


function startWorker() {

	let worker = new Worker("/assets/js/mandelbrot_worker.bundle.js");

	let mandelbrot = document.getElementById("mandelbrot")
	mandelbrot.setAttribute("style", "width:100%;")
	mandelbrot.style.height = Math.floor(mandelbrot.offsetWidth) + 'px';
	mandelbrot.style.backgroundColor = "rgba(0,0,0, 0.04)";

	let draw = SVG('mandelbrot').size(mandelbrot.offsetWidth, mandelbrot.offsetWidth)

	let mWidth = mandelbrot.clientWidth
	let mHeight = mandelbrot.clientHeight
	
	var mouseIsDown = false
	
	function handleMouseEvent(event) {
		let relativeX = (event.pageX - window.pageXOffset - mandelbrot.getBoundingClientRect().left);
		let relativeY = (event.pageY - window.pageYOffset - mandelbrot.getBoundingClientRect().top);
		let scaledX = relativeX / mWidth * 4.0 - 2.0
		let scaledY = relativeY / mHeight * 4.0 - 2.0
		worker.postMessage({x: scaledX, y: scaledY})
		event.preventDefault()
	}
	
	function handleTouchEvent(event) {
		let touch = event.touches[0]

		let relativeX = touch.pageX - window.pageXOffset - mandelbrot.getBoundingClientRect().left
		let relativeY = touch.pageY - window.pageYOffset - mandelbrot.getBoundingClientRect().top
		let scaledX = relativeX / mWidth * 4.0 - 2.0
		let scaledY = relativeY / mHeight * 4.0 - 2.0
		worker.postMessage({x: scaledX, y: scaledY})
		event.preventDefault()
	}
	
	mandelbrot.onclick = function(event) {
		handleMouseEvent(event)
	}
	
	mandelbrot.onmousedown = function(event) {
		mouseIsDown = true
		draw.clear()
		event.preventDefault()
	}
	mandelbrot.onmouseup = function(event) {
		mouseIsDown = false
		//draw.clear()
		event.preventDefault()
	}
	mandelbrot.onmousemove = function(event) {
		if (mouseIsDown) {
			handleMouseEvent(event)
		}
		event.preventDefault()
	}
	
	document.addEventListener('touchmove', handleTouchEvent, { passive:false });

	let sequence = 0
	
	worker.onmessage = function(event) {
	
		if (event.data["sequence"] >= sequence) {
			sequence = event.data["sequence"]
		} else {
			return
		}
		
		if (event.data["orbits"]) {
			draw.clear()
			let xs = event.data["orbits"][0]
			let ys = event.data["orbits"][1]
			let iterations = event.data["iterations"]
			for (var i = 1; i < iterations; i++) {
				let xOffset = (xs[i] + 2.0) / 4.0 * mWidth
				let yOffset = (ys[i] + 2.0) / 4.0 * mHeight
				if (xOffset > 0 && xOffset < mWidth && yOffset > 0 && yOffset < mHeight) {
	 				draw.rect(3, 3).fill('#000000').translate(xOffset, yOffset)
				}
			}
		} else if (event.data["message"]) {
			showText(event.data["message"]);
		}
	};
}

if (typeof(Worker) !== "undefined") {
	startWorker()
} else {
	showText("Sorry, Web Worker is not supported in your browser. :(")	
}

mandelbrot_worker.ts

declare function postMessage(message: any):any

var sequence = 0
let max_iterations = 256
let bailout = 16

let iterationBufferZR = new Float32Array(max_iterations)
let iterationBufferZI = new Float32Array(max_iterations)

function mandelbrot(xx: number, yy: number) {
	
	let cr = xx
	let ci = yy
	let zi = 0.0
	let zr = 0.0

	let i = 0
	
	while (i++ < max_iterations) {
		let temp = zr * zi
		let zr2 = zr * zr
		let zi2 = zi * zi
		zr = zr2 - zi2 + cr
		zi = 2.0 * temp + ci
		if (zi2 + zr2 > bailout) {
			return i
		}
		
		iterationBufferZR[i] = zr
		iterationBufferZI[i] = zi
	}
	return i
}


self.onmessage = function (msg: any) {
	let x = msg.data["x"]
	let y = msg.data["y"]
	let iterations = mandelbrot(x, y)
	
	sequence = sequence + 1
	
	postMessage({
		orbits: [iterationBufferZR, iterationBufferZI],
		iterations: iterations,
		sequence: sequence
	})
	
}

postMessage({
	message: "Click or Tap in the gray square.",
	sequence: sequence	
});