Skip to content

Instantly share code, notes, and snippets.

@mootari
Last active June 14, 2017 22:04
Show Gist options
  • Save mootari/fb37437067fbfaa7ad3d23c07d726057 to your computer and use it in GitHub Desktop.
Save mootari/fb37437067fbfaa7ad3d23c07d726057 to your computer and use it in GitHub Desktop.
Random trees without RNGs
<!DOCTYPE html>
<html>
<head>
<style>
canvas {display:block; margin: 20px auto; border: 2px solid #4892c6; max-width: 100%; box-sizing: border-box; }
</style>
</head>
<body>
<script>(function() {
'use strict';
var _options = {
// Everything above -3 is boring
cellIndex: -41,
// Range: -3 to 3
edgeIndex: -2,
// Cells per axis
width: 160,
height: 80,
// Render scale in px
scale: 6,
// Rate of depth color change
depthScale: .03,
// Iteration delay
delay: 10,
renderInterval: 25,
drawDepth: true,
drawLines: true
};
var _stack = [];
var _points = new Map();
var _live = document.createElement('canvas').getContext('2d');
_live.canvas.width = _options.width * _options.scale;
_live.canvas.height = _options.height * _options.scale;
document.body.appendChild(_live.canvas);
var _offscreen = _live.canvas.cloneNode().getContext('2d');
_offscreen.lineWidth = _options.scale * .2;
_offscreen.lineCap = 'round';
_offscreen.strokeStyle = '#000';
var root = addPoint([~~(_options.width / 2), ~~(_options.height / 2)]);
renderPoint(_offscreen, root);
play();
function play() {
var i = _options.renderInterval;
while(_stack.length) {
var point = iterate();
if(point) {
renderPoint(_offscreen, point);
if(--i <= 0 && _options.delay >= 0) {
setTimeout(play, _options.delay);
publish();
return;
}
}
}
publish();
}
function iterate() {
var added;
var cell = removeIndex(_stack, _options.cellIndex);
var edges = mask(cell)
.filter(isInBounds)
.filter(isAvailable);
var edge = removeIndex(edges, _options.edgeIndex);
if(edge) {
added = addPoint(edge, cell);
if(edges.length) {
_stack.push(cell);
}
}
return added;
}
// Registers and initializes the given point and adds it to the stack.
function addPoint(point, parent) {
var o = getOffset(point);
var p = point.slice();
p.parent = parent || null;
p.depth = parent ? parent.depth + 1 : 0;
_points.set(o, p);
_stack.push(p);
return p;
}
// Returns a string ID for a point.
function getOffset(point) {
return point.join(',');
}
// Checks if a point is inside the extent.
function isInBounds(point) {
return point[0] >= 0 && point[0] < _options.width
&& point[1] >= 0 && point[1] < _options.height;
}
// Checks if a point's offset is not yet blocked.
function isAvailable(point) {
return !_points.has(getOffset(point));
}
// Normalizes an index and extracts the element at the resulting index.
function removeIndex(arr, index) {
if(!arr.length) {
return null;
}
var i = index < 0
? Math.max(0, arr.length + index)
: Math.min(arr.length - 1, index);
return arr.splice(i, 1)[0];
}
// Produces edge coordinates for a point.
function mask(point) {
var mask = [
[ 1, 0],
[ 0, 1],
[-1, 0],
[ 0, -1]
];
var i = mask.length;
while(i--) {
mask[i][0] += point[0];
mask[i][1] += point[1];
}
return mask;
}
function renderPoint(ctx, point) {
var s = _options.scale, sh = s / 2;
var x = point[0] * s;
var y = point[1] * s;
if(_options.drawDepth) {
var level = ~~(70 + 185 * Math.abs(Math.cos(point.depth * _options.depthScale)));
ctx.fillStyle = 'rgb(' + level + ',' + level + ',' + level + ')';
ctx.fillRect(x, y, s, s);
}
if(_options.drawLines && point.parent) {
ctx.beginPath();
ctx.moveTo(point.parent[0] * s + sh, point.parent[1] * s + sh);
ctx.lineTo(x + sh, y + sh);
ctx.stroke();
}
}
function publish() {
_live.clearRect(0, 0, _live.canvas.width, _live.canvas.height);
_live.drawImage(_offscreen.canvas, 0, 0);
}
}());</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment