diff --git a/scripts/index.js b/scripts/index.js index 6a8a60c..1f9f515 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -2,6 +2,7 @@ import { html } from 'the-dom'; import { applyChaos } from './chaos'; +import { barnsley } from './ifs'; const { body } = html(document); @@ -9,22 +10,18 @@ const content = body.find('#content'); const plotting = body.find('#plotting').node; const ctx = plotting.getContext('2d'); -const padding = 40; // padding between the canvas edges and the points +let dragging = false; +let center, zoom = 200; let width, height; -const linearTransform = (a, b, c, d, e, f) => point => [ - a * point[0] + b * point[1] + e, - c * point[0] + d * point[1] + f -]; - /** * Re-render the scene from scratch * * @return {null} */ const render = () => { - plotting.width = width + 2 * padding; - plotting.height = height + 2 * padding; + plotting.width = width; + plotting.height = height; // do not plot (very) small sizes if (width < 1) { @@ -32,16 +29,27 @@ const render = () => { } // do the chaos game - const image = ctx.getImageData(padding, padding, width, height); + const image = ctx.getImageData(0, 0, width, height); + const color = [0, 0, 0]; + let skip = 50; - applyChaos(image, [0, 0], 500000, [ - linearTransform(0, 0, 0, 0.16, 0, 0), - linearTransform(.85, .04, -.04, .85, 0, 1.6), - linearTransform(.20, -.26, .23, .22, 0, 1.6), - linearTransform(-.15, .28, .26, .24, 0, .44) - ], [.01, .85, .07, .07]); + applyChaos(image, [0, 0], 500000, ...barnsley, point => { + const x = Math.floor(point[0] * zoom + center[0]); + const y = height - Math.floor(point[1] * zoom + center[1]); - ctx.putImageData(image, padding, padding); + if (x >= 0 && x < width && y >= 0 && y < height && skip <= 0) { + const i = (y * width + x) * 4; + + image.data[i] = color[0]; + image.data[i + 1] = color[1]; + image.data[i + 2] = color[2]; + image.data[i + 3] = 255; + } + + skip -= 1; + }); + + ctx.putImageData(image, 0, 0); }; /** @@ -50,11 +58,60 @@ const render = () => { * @return {null} */ const resize = () => { - width = content.node.clientWidth - 2 * padding; - height = content.node.clientHeight - 2 * padding; + width = content.node.clientWidth; + height = content.node.clientHeight; + center = [Math.floor(width / 2), Math.floor(height / 2)]; render(); }; +/** + * Zoom on the cursor position when using mouse wheel + */ +content.on('wheel', event => { + const delta = event.deltaMode === 0 ? event.deltaY / 53 : event.deltaY; + const newZoom = zoom * (1 - delta * .035); + + // which (unprojected) point does the mouse point on? + const mouse = [ + (event.offsetX - center[0]) / zoom, + (height - event.offsetY - center[1]) / zoom + ]; + + // we need to set the center so that `mouse` stays at + // the same position on screen, i.e (vectorially): + // mouse * newZoom + newCenter = mouse * zoom + center + // => newCenter = mouse * zoom - mouse * newZoom + center + center = [ + mouse[0] * zoom - mouse[0] * newZoom + center[0], + mouse[1] * zoom - mouse[1] * newZoom + center[1] + ]; + + zoom = newZoom; + render(); + + event.preventDefault(); +}); + +/** + * Pan the content with click-drag action + */ +content.on('mousedown', event => dragging = [event.offsetX, event.offsetY]); +content.on('mouseup', () => dragging = false); +content.on('mousemove', event => { + if (dragging !== false) { + const newMouse = [event.offsetX, event.offsetY]; + const movement = [newMouse[0] - dragging[0], newMouse[1] - dragging[1]]; + + center[0] += movement[0]; + center[1] -= movement[1]; + + render(); + dragging = newMouse; + + event.preventDefault(); + } +}); + window.onresize = resize; resize();