💡 Rewrite main script with ES6, remove worker usage
This commit is contained in:
parent
b18344a217
commit
a0d614e06d
|
@ -0,0 +1,135 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { body, create } from './dom';
|
||||||
|
import { randomNumber, randomColor } from './utils';
|
||||||
|
|
||||||
|
const content = body.get('#content');
|
||||||
|
const plotting = body.get('#plotting');
|
||||||
|
const ctx = plotting.node.getContext('2d');
|
||||||
|
|
||||||
|
const padding = 40; // padding between the canvas edges and the points
|
||||||
|
let image, width, height;
|
||||||
|
let lastUpdate = -Infinity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a fractal of given width, height, based on
|
||||||
|
* a polygon of given amount of vertices, using the
|
||||||
|
* chaos game applied with given fraction
|
||||||
|
*
|
||||||
|
* @param {number} width Fractal width
|
||||||
|
* @param {number} height Fractal height
|
||||||
|
* @param {number} fraction Fraction to use
|
||||||
|
* @param {Array} colors Color of each vertex
|
||||||
|
* @return {ImageData} Generated pixel data
|
||||||
|
*/
|
||||||
|
const chaos = (width, height, fraction, colors) => {
|
||||||
|
const cx = Math.floor(width / 2);
|
||||||
|
const cy = Math.floor(height / 2);
|
||||||
|
const radius = Math.min(cx, cy);
|
||||||
|
|
||||||
|
const count = colors.length;
|
||||||
|
const vertices = [];
|
||||||
|
const angleStep = 2 * Math.PI / count;
|
||||||
|
let initialAngle;
|
||||||
|
|
||||||
|
// creating 0-width image data will throw an error
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return ctx.createImageData(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = ctx.createImageData(width, height);
|
||||||
|
const data = image.data;
|
||||||
|
|
||||||
|
// we will rotate around an inscribed circle to calculate
|
||||||
|
// the vertices' positions. We adapt the initial angle so
|
||||||
|
// that usual polygons look better
|
||||||
|
if (count === 3) {
|
||||||
|
initialAngle = -Math.PI / 2;
|
||||||
|
} else if (count === 4) {
|
||||||
|
initialAngle = Math.PI / 4;
|
||||||
|
} else {
|
||||||
|
initialAngle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i += 1) {
|
||||||
|
let current = angleStep * i + initialAngle;
|
||||||
|
|
||||||
|
vertices.push([
|
||||||
|
Math.floor(Math.cos(current) * radius + cx),
|
||||||
|
Math.floor(Math.sin(current) * radius + cy)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we apply the chaos algorithm:
|
||||||
|
// for any point, the next point is a `fraction` of the
|
||||||
|
// distance between it and a random vertex
|
||||||
|
let point = vertices[0];
|
||||||
|
let iterations = 200000;
|
||||||
|
let drop = 1000;
|
||||||
|
|
||||||
|
while (iterations--) {
|
||||||
|
const vertexNumber = randomNumber(0, count);
|
||||||
|
const vertex = vertices[vertexNumber], color = colors[vertexNumber];
|
||||||
|
|
||||||
|
point = [
|
||||||
|
Math.floor((point[0] - vertex[0]) * fraction + vertex[0]),
|
||||||
|
Math.floor((point[1] - vertex[1]) * fraction + vertex[1])
|
||||||
|
];
|
||||||
|
|
||||||
|
// skip the first 1000 points
|
||||||
|
if (drop === 0) {
|
||||||
|
const i = (point[1] * width + point[0]) * 4;
|
||||||
|
|
||||||
|
data[i] = color[0];
|
||||||
|
data[i + 1] = color[1];
|
||||||
|
data[i + 2] = color[2];
|
||||||
|
data[i + 3] = 255;
|
||||||
|
} else {
|
||||||
|
drop--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the scene, recalculating the points
|
||||||
|
* positions if they need to
|
||||||
|
*
|
||||||
|
* @return {null}
|
||||||
|
*/
|
||||||
|
const render = () => {
|
||||||
|
// only recalculate every 16.67 ms
|
||||||
|
if (+new Date() - lastUpdate > 16.67) {
|
||||||
|
image = chaos(
|
||||||
|
width - 2 * padding, height - 2 * padding, 1/2,
|
||||||
|
[[255, 0, 0], [0, 255, 0], [0, 0, 255]]
|
||||||
|
);
|
||||||
|
|
||||||
|
lastUpdate = +new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
ctx.putImageData(
|
||||||
|
image, padding, padding
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize the canvas to fit the new
|
||||||
|
* window size and redraw the scene
|
||||||
|
*
|
||||||
|
* @return {null}
|
||||||
|
*/
|
||||||
|
const resize = () => {
|
||||||
|
width = content.node.clientWidth;
|
||||||
|
height = content.node.clientHeight;
|
||||||
|
|
||||||
|
plotting.setAttr('width', width);
|
||||||
|
plotting.setAttr('height', height);
|
||||||
|
|
||||||
|
render();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onresize = resize;
|
||||||
|
resize();
|
|
@ -1,212 +0,0 @@
|
||||||
/*jslint browser:true, nomen:true, plusplus:true, devel:true */
|
|
||||||
/*globals $, utils */
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var jCanvas = $('#canvas'), canvas = jCanvas[0], context = canvas.getContext('2d'),
|
|
||||||
chaos = new window.Worker('js/chaos.js'), button = $('#pt-gen'), count = $('#pt-num'),
|
|
||||||
size_input = $('#pt-size'), render = $('#pt-render'), render_link = render.find('a'),
|
|
||||||
vertices_count = $('#pt-vertices'), factor_top = $('#pt-frac-top'), factor_bottom = $('#pt-frac-bottom'),
|
|
||||||
form_inputs = $('#parameters form p :input'), padding = 10, verticesColors = [],
|
|
||||||
zoom = 1, zoom_out = $('#zoom-out'), zoom_in = $('#zoom-in');
|
|
||||||
|
|
||||||
// presets
|
|
||||||
$('.setting').click(function (e) {
|
|
||||||
var id = parseInt($(this).attr('data-setting-id'), 10);
|
|
||||||
|
|
||||||
if ($('#parameters form').prop('disabled')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
case 1:
|
|
||||||
vertices_count.val(3);
|
|
||||||
factor_top.val(1);
|
|
||||||
factor_bottom.val(2);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
vertices_count.val(5);
|
|
||||||
factor_top.val(3);
|
|
||||||
factor_bottom.val(8);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
vertices_count.val(6);
|
|
||||||
factor_top.val(1);
|
|
||||||
factor_bottom.val(3);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
vertices_count.val(5);
|
|
||||||
factor_top.val(1);
|
|
||||||
factor_bottom.val(3);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
// zoom level
|
|
||||||
function updateZoomLevel() {
|
|
||||||
jCanvas.css({
|
|
||||||
width: canvas.width * zoom,
|
|
||||||
height: canvas.height * zoom
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setZoomLevel(level) {
|
|
||||||
zoom = level;
|
|
||||||
|
|
||||||
updateZoomLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
zoom_in.click(function () {
|
|
||||||
if (zoom < 2) {
|
|
||||||
setZoomLevel(zoom + 0.1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
zoom_out.click(function () {
|
|
||||||
if (zoom > 0.1) {
|
|
||||||
setZoomLevel(zoom - 0.1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// returns the position of the vertices of a regular polygon with n angles
|
|
||||||
// the width and height parameter specifies the radius of the inscripting circle
|
|
||||||
function getRegularVertices(width, height, vertices) {
|
|
||||||
var i = 0, shapeVerts = [], angle, x = width / 2, y = height / 2,
|
|
||||||
rx = width / 2 - padding, ry = height / 2 - padding,
|
|
||||||
frac = Math.PI * 2 / vertices;
|
|
||||||
|
|
||||||
for (i = 0; i < vertices; i++) {
|
|
||||||
angle = frac * i + Math.PI / 2;
|
|
||||||
shapeVerts.push([
|
|
||||||
Math.cos(angle) * rx + x,
|
|
||||||
Math.sin(angle) * ry + y
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return shapeVerts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// updates rendering button which allows to download the image
|
|
||||||
function updateRender() {
|
|
||||||
render_link.attr('href', canvas.toDataURL());
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the vertices of the current polygon - wth the lines
|
|
||||||
function canvasDrawVertices() {
|
|
||||||
var width = canvas.width, height = canvas.height,
|
|
||||||
vertices = getRegularVertices(width, height, parseInt(vertices_count.val(), 10)),
|
|
||||||
vertices_num = vertices.length, i, vertex,
|
|
||||||
lastVertex = vertices[vertices_num - 1];
|
|
||||||
|
|
||||||
context.clearRect(0, 0, width, height);
|
|
||||||
|
|
||||||
// one color per vertex
|
|
||||||
verticesColors = [];
|
|
||||||
|
|
||||||
for (i = 0; i < vertices_num; i++) {
|
|
||||||
verticesColors.push(utils.getRandomColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw lines
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(lastVertex[0], height - lastVertex[1]);
|
|
||||||
|
|
||||||
for (i = 0; i < vertices_num; i++) {
|
|
||||||
vertex = vertices[i];
|
|
||||||
context.lineTo(vertex[0], height - vertex[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.stroke();
|
|
||||||
|
|
||||||
// draw vertices
|
|
||||||
for (i = 0; i < vertices_num; i++) {
|
|
||||||
vertex = vertices[i];
|
|
||||||
context.fillStyle = utils.rgbToHex(verticesColors[i]);
|
|
||||||
|
|
||||||
context.beginPath();
|
|
||||||
context.arc(vertex[0], height - vertex[1], 3, 0, Math.PI * 2, true);
|
|
||||||
context.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableForm(enabled) {
|
|
||||||
form_inputs.prop('disabled', !enabled);
|
|
||||||
$('#parameters form').prop('disabled', !enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// on generate response from the worker
|
|
||||||
$(chaos).on('message', function (e) {
|
|
||||||
var points = e.originalEvent.data, length = points.length, i,
|
|
||||||
data, pdata, point, index, width = canvas.width,
|
|
||||||
vertexColor, height = canvas.height,
|
|
||||||
vertices = parseInt(vertices_count.val(), 10);
|
|
||||||
|
|
||||||
if (!Array.isArray(points[0])) {
|
|
||||||
console.error('Error: ' + points);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// about the fastest way to draw points array on a canvas:
|
|
||||||
// http://jsperf.com/filling-a-bunch-of-points-in-canvas
|
|
||||||
data = context.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
for (i = 0; i < length; i++) {
|
|
||||||
point = points[i];
|
|
||||||
pdata = point[0];
|
|
||||||
vertexColor = verticesColors[point[1]];
|
|
||||||
|
|
||||||
index = (parseInt(pdata[0], 10) + (height - parseInt(pdata[1], 10)) * width) * 4;
|
|
||||||
|
|
||||||
data.data[index] = vertexColor[0];
|
|
||||||
data.data[index + 1] = vertexColor[1];
|
|
||||||
data.data[index + 2] = vertexColor[2];
|
|
||||||
data.data[index + 3] = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.putImageData(data, 0, 0);
|
|
||||||
enableForm(true);
|
|
||||||
updateRender();
|
|
||||||
});
|
|
||||||
|
|
||||||
// set input size
|
|
||||||
size_input.change(function () {
|
|
||||||
var size = parseInt($(this).val(), 10);
|
|
||||||
|
|
||||||
canvas.width = size + 2 * padding;
|
|
||||||
canvas.height = size + 2 * padding;
|
|
||||||
|
|
||||||
canvasDrawVertices();
|
|
||||||
updateZoomLevel();
|
|
||||||
});
|
|
||||||
|
|
||||||
// generate
|
|
||||||
button.click(function (e) {
|
|
||||||
var width = canvas.width, height = canvas.height, shape,
|
|
||||||
vertices = parseInt(vertices_count.val(), 10),
|
|
||||||
frac = parseInt(factor_top.val(), 10) / parseInt(factor_bottom.val(), 10);
|
|
||||||
|
|
||||||
// reset environment
|
|
||||||
enableForm(false);
|
|
||||||
canvasDrawVertices();
|
|
||||||
|
|
||||||
// regular polygon
|
|
||||||
shape = getRegularVertices(width, height, vertices);
|
|
||||||
|
|
||||||
chaos.postMessage([
|
|
||||||
parseInt(count.val(), 10) * width,
|
|
||||||
[width, height],
|
|
||||||
shape,
|
|
||||||
frac
|
|
||||||
]);
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
vertices_count.change(canvasDrawVertices);
|
|
||||||
canvasDrawVertices();
|
|
||||||
}());
|
|
Loading…
Reference in New Issue