Mattéo Delabre
8 years ago
2 changed files with 135 additions and 212 deletions
@ -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