Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase performance & make it compatible with more browsers #1

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
# Created by .ignore support plugin (hsz.mobi)
.idea
.DS_Store
20 changes: 10 additions & 10 deletions index.html
@@ -1,18 +1,18 @@
<!DOCTYPE html>
<html>

<head>
<script
src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/randomcolor/0.5.4/randomColor.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8"/>
<meta charset="utf-8" />

</head>

<body>
<script src="js/util.js"></script>
<script src="js/sketch.js"></script>
<canvas id="fractal"></canvas>
<script src="js/compy-stuff.js"></script>
<script src="js/util.js"></script>
<script src="js/painter.js"></script>
<script src="js/sketch.js"></script>
</body>
</html>

</html>
98 changes: 59 additions & 39 deletions js/acolyte.js
@@ -1,13 +1,10 @@
importScripts('https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js')
importScripts('/js/compy-stuff.js')
importScripts('compy-stuff.js')

let id
let canvas
let ctx
let xBounds
let yBounds
let xOffset
let bounds
let offset
let maxDimensions
let dimensions
let maxIters

let drawing = false
Expand All @@ -17,57 +14,80 @@ self.onmessage = function onmessage(event) {
switch (event.data.message) {

case 'setup':
({id, canvas, xOffset, xBounds, yBounds, maxDimensions, maxIters} = event.data)
ctx = canvas.getContext('2d')

({ id, offset, bounds, dimensions, maxDimensions, maxIters } = event.data)
draw()
break

case 'draw':
({ xBounds, yBounds, maxIters } = event.data)
({ bounds, maxIters } = event.data)
draw()
}
}

function draw() {
const start = performance.now();
const data = new Uint8ClampedArray(dimensions.width * dimensions.height * 4);
const colormap = makeColorMap(maxIters);

if (drawing) {
return
}

if (!canvas) {
return
}

drawing = true
const c = new Complex(0, 0);

for (let x = 0; x < canvas.width; x++) {
for (let y = 0; y < canvas.height; y++) {
for (let i = 0; i < data.length; i += 4) {
let x = offset.x + (i / 4) % dimensions.width;
let y = offset.y + (i / 4) / dimensions.width | 0;
lovasoa marked this conversation as resolved.
Show resolved Hide resolved

const ax = x + xOffset
c.re = map(x, 0, maxDimensions.width, bounds.x.min, bounds.x.max);
c.im = map(y, 0, maxDimensions.height, bounds.y.min, bounds.y.max);

const c = {
re: map(ax, 0, maxDimensions.width, xBounds.min, xBounds.max),
im: map(y, 0, maxDimensions.height, yBounds.min, yBounds.max)
}
const iterBeforeCollapse = testMandelbrot(c, maxIters)

const result = testMandelbrot(c, maxIters)
if (iterBeforeCollapse < maxIters) {
const idx = iterBeforeCollapse * 3;
const color = colormap.subarray(idx, idx + 3);
data.set(color, i);
}
data[i + 3] = 255; // alpha
}

if (result.collapses) {
ctx.fillStyle = '#000000'
} else {
const buffer = data.buffer;
const time = performance.now() - start;
self.postMessage({ message: 'draw', buffer, time }, [buffer]);
}

const hue = map(result.iterBeforeCollapse, 0, maxIters, 0, 360)
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`
}

ctx.fillRect(x, y, 1, 1)
const colormaps = [];
function makeColorMap(size) {
if (!colormaps[size]) {
const colormap = new Uint8ClampedArray(size * 3);
for (let i = 0; i < size; i++) {
const rgb = hslToRgb(i / size, 1, .5);
colormap.set(rgb, i * 3);
}
colormaps[size] = colormap;
}
return colormaps[size];
}

const img = ctx.getImageData(0, 0, canvas.width, canvas.height)

self.postMessage({ message: 'draw', img })
function hslToRgb(h, s, l) {
var r, g, b;

if (s == 0) {
r = g = b = l; // achromatic
} else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}

drawing = false
return [r * 255, g * 255, b * 255];
}

function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
121 changes: 40 additions & 81 deletions js/compy-stuff.js
@@ -1,100 +1,59 @@
/**
* @typedef {Object} Complex
*
* @property {number} re
* @property {number} im
*/

/**
* @typedef {Object} MandeltestResult
*
* @property {boolean} collapses
* @property {number} iterBeforeCollapse
*/

/**
* @param {Complex} c
* @param {number} [maxIter = 20]
* @returns {MandeltestResult}
*/
function testMandelbrot(c, maxIter = 200) {

let z = {
re: 0,
im: 0
class Complex {
constructor(re, im) {
this.re = re;
this.im = im;
}

const result = {
iterBeforeCollapse: 0,
collapses: true
add(other) {
this.re += other.re;
this.im += other.im;
}

for (let i = 0; i < maxIter; i++) {

result.iterBeforeCollapse++

z = complexAdd(complexMult(z, z), c)

if (complexAbs(z) > 2) {
result.collapses = false
break
}

square() {
const re = this.re * this.re - this.im * this.im;
this.im = this.re * this.im * 2;
this.re = re;
}

return result
squareAbs() {
return this.re * this.re + this.im * this.im;
}
}

/**
* @param {Complex} c
* @param {{width: number, height: number}} dimensions
* @param {{min: number, max: number}} xBounds
* @param {{min: number, max: number}} yBounds
* @param {number} maxIters
* @param {number} [maxIter = 20]
* @returns {number}
*/
function determineHue(c, dimensions, xBounds, yBounds, maxIters = 200) {

const x = c.x
const y = c.y

let a = map(x, 0, dimensions.width, xBounds.min, xBounds.max)
let b = map(y, 0, dimensions.height, yBounds.min, yBounds.max)

const result = testMandelbrot({ re: a, im: b }, maxIters)

if (result.collapses) {
return [0]
} else {

const hue = 360 - map(result.iterBeforeCollapse, 0, maxIters, 0, 360)
return [hue, 255, 255]
function testMandelbrot(c, maxIter = 200) {
if (!testCardioid(c) && !testBulb(c)) {
let z = new Complex(0, 0);
for (let i = 0; i < maxIter; i++) {
z.square();
z.add(c);
if (z.squareAbs() > 4) return i;
}
}
return maxIter;
}

function complexAdd(num1, num2) {

const result = {}

result.re = num1.re + num2.re
result.im = num1.im + num2.im

return result
}

function complexMult(num1, num2) {

const result = {}

result.re = (num1.re * num2.re) - (num1.im * num2.im)
result.im = (num1.re * num2.im) + (num1.im * num2.re)

return result
/**
* @param {Complex} c
* @returns {boolean}
*/
function testCardioid(c) {
lovasoa marked this conversation as resolved.
Show resolved Hide resolved
const a = (c.re - 1 / 4);
const q = a * a + c.im * c.im;
return q * (q + a) <= .25 * c.im * c.im;
}

function complexAbs(num) {
return Math.sqrt(
Math.pow(num.re, 2) + Math.pow(num.im, 2)
)
/**
* @param {Complex} c
* @returns {boolean}
*/
function testBulb(c) {
const a = c.re + 1;
return a * a + c.im * c.im <= 1 / 16;
}

function map(n, start1, stop1, start2, stop2, withinBounds) {
Expand Down