diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 0000000..8f39461 --- /dev/null +++ b/scripts/index.js @@ -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(); diff --git a/scripts/script.js b/scripts/script.js deleted file mode 100644 index 54a2257..0000000 --- a/scripts/script.js +++ /dev/null @@ -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(); -}()); \ No newline at end of file