Skip to content

Instantly share code, notes, and snippets.

@keville
Last active January 21, 2019 17:41
Show Gist options
  • Save keville/1ebf22ed453166f96dd45fae333587ae to your computer and use it in GitHub Desktop.
Save keville/1ebf22ed453166f96dd45fae333587ae to your computer and use it in GitHub Desktop.
ToyBox
<!DOCTYPE html>
<html>
<head>
<title>toybox</title>
<style type="text/css">
html,
body {
margin: 0;
padding: 0;
background-color: #000;
width: 100%;
height: 100%;
}
body > canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script type="text/javascript">
const sqrt3over2 = Math.sqrt(3) / 2;
function randomInRange(min = 0, max = 1) {
return Math.random() * (max - min) + min;
}
function randomIntInRange(min = 0, max = 1) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function coinToss() {
return Math.random() >= 0.5;
}
function randomColor(
r = randomIntInRange(127, 255),
g = randomIntInRange(127, 255),
b = randomIntInRange(127, 255),
a = randomInRange(0.8, 1)
) {
// const rr = r.toString(16).repeat(2);
// const gg = g.toString(16).repeat(2);
// const bb = b.toString(16).repeat(2);
const aa = a.toFixed(3);
return `rgba(${r}, ${g}, ${b}, ${aa})`;
}
function randomGray(
a = randomInRange(0.3, 1)
) {
const aa = a.toFixed(3);
return `rgba(255, 255, 255, ${aa})`;
}
function _rgbRotate(num) {
switch (num % 3) {
case 0:
return randomColor(undefined, 0, 0);
case 1:
return randomColor(0, undefined, 0);
case 2:
return randomColor(0, 0, undefined);
}
}
function _rotatedColorsByChunk(size) {
const arr = [];
while (size--) {
arr.push(_rgbRotate(size));
}
return arr;
}
class Application {
constructor(painters) {
this.painters = painters;
this.canvas = new Canvas(document.body, 0, 0);
this.viewport = new Viewport(this);
this.animate();
}
viewportResized(viewport, w, h) {
this.canvas.resize(w, h);
this.paint(-1);
}
animate() {
window.requestAnimationFrame((t) => this.paint(t));
}
paint(t) {
const { canvas } = this;
const { context, width, height } = canvas;
this.painters.forEach((painter) => painter.paint(t, width, height, canvas, context));
}
};
class Viewport {
constructor(app) {
this.app = app;
window.addEventListener('resize', (e) => this.handleResize());
this.handleResize();
}
handleResize() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.app.viewportResized(this, this.width, this.height);
}
};
class Canvas {
constructor(parent, w, h) {
this.element = document.createElement('canvas');
this.context = this.element.getContext('2d');
parent.appendChild(this.element);
this.resize(w, h);
}
resize(w, h) {
this.width = w;
this.height = h;
this.element.width = w;
this.element.height = h;
this.element.style.width = `${w}px`;
this.element.style.height = `${h}px`;
}
}
class Painter {
constructor(paintFn) {
this.paintFn = paintFn;
}
resize(w, h) {
if (this.width === w && this.height === h) {
return false;
}
this.width = w;
this.height = h;
return true;
}
paint(t, w, h, canvas, context) {
this.resize(w, h);
this.paintFn.call(this, t, w, h, canvas, context);
}
};
function xyForNext(dist, angle) {
const rads = angle * Math.PI / 180;
return [
dist * Math.cos(rads),
dist * Math.sin(rads)
];
}
const ROTATE_CW = 1;
const ROTATE_CCW = -1;
const CONTINUE_CW = undefined;
// 1, 1, 2, 1, 2, 2
// 2, 2, 3, 2, 3, 3
// 3, 3, 4, 3, 4, 4
function _getRotation(index, wraps, side, step, flips) {
let direction;
switch (step) {
case 0:
direction = ROTATE_CCW;
break;
case 1:
direction = ROTATE_CW;
break;
case 2:
direction = CONTINUE_CW;
break;
default:
direction = step % 0 ? ROTATE_CCW : ROTATE_CW;
}
if (index === 15) {
debugger;
}
if (direction === ROTATE_CCW) {
flips = flips + 1;
}
step = step + 1;
switch (side) {
case 0:
case 1:
case 3:
// one flip allowed
if (flips === 1 && direction === ROTATE_CW) {
flips = 0;
step = 0;
}
break;
case 2:
case 4:
case 5:
// two flips allowed
if (flips === 2 && direction === ROTATE_CW) {
flips = 0;
step = 0;
}
if (side === 5) {
side = 0;
wraps = wraps + 1;
}
break;
}
return [
side,
step,
direction,
flips,
wraps
];
}
const MANUAL_ROTATIONS = [
ROTATE_CW,
CONTINUE_CW,
CONTINUE_CW,
CONTINUE_CW,
CONTINUE_CW,
CONTINUE_CW, // end hex, 0 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
CONTINUE_CW, // end edge, 1 flip
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW, // end side, 1 flips, end first wrapping
];
const _ROTS = [
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW, // end side, 1 flip
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW, // end side, 1 flip
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 2 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW, // end side, 1 flip
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 2 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 2 flips, end second wrapping
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 2 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 2 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 3 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 2 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 3 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 3 flips, end third wrapping
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 3 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 3 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 4 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 3 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 4 flips
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end side, 4 flips, end fourth wrapping
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end fifth wrapping
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end sixth wrapping
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
CONTINUE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW,
ROTATE_CCW,
ROTATE_CW, // end seventh wrapping
];
// 0 flips = hex = tris: 6 = 1 + 5
// +2 2 flips = wrapped triangle = tris: 13 = 1 + 12
// +1 (+1) 3 flips = rhomboid = tris: 16 = 1 + 12 + 3
// +9 (+6) 11 flips = next wrapping = tris: 37 = 1 + 12 + 24
// +15 (+6) 26 flips = next wrapping = tris: 73 = 1 + 12 + 24 + 36
// +21 (+6) 47 flips = next wrapping = tris: 121 = 1 + 12 + 24 + 36 + 48
// +27 (+6) 74 flips = next wrapping = tris: 181 = 1 + 12 + 24 + 36 + 48 + 60
// +33 (+6) 107 flips = next wrapping = tris: 253 = 1 + 12 + 24 + 36 + 48 + 60 + 72
// +39 (+6) 146 flips = next wrapping = tris: 337 = 1 + 12 + 24 + 36 + 48 + 60 + 72 + 84
// const flipLimit = 11;
// let limit = NaN;
// let flips = 0;
// MANUAL_ROTATIONS.every((rot, i, arr) => {
// if (rot !== ROTATE_CCW) {
// limit = i;
// return true;
// }
// return ++flips <= flipLimit;
// });
const manualCount = MANUAL_ROTATIONS.length;
let limit = 15;
ROTATIONS = MANUAL_ROTATIONS.slice(0, Math.max(limit, manualCount));
console.log(`manual count: ${manualCount} / limit: ${limit + 1}`);
class FunkyPainter extends Painter {
constructor(paintFn) {
super(paintFn);
this.radius = 40;
this.margin = 0;
}
resize(w, h) {
if (!super.resize(w, h)) {
return;
}
const originX = Math.floor(w / 2);
const originY = Math.floor(h / 2);
const { radius, margin } = this;
const tris = [];
let index = -1;
let side = 0;
let step = 0;
let flips = 0;
let wraps = 1;
let angle = -30;
let rotate = 60;
let direction;
let ox = originX;
let oy = originY;
const _families = [];
while (index++ < limit) {
if (index < 13) {
const _manualRot = ROTATIONS[index];
if (_manualRot !== undefined) {
direction = _manualRot;
}
}
else {
const [
nextSide,
nextStep,
nextDirection,
nextFlips,
nextWraps
] = _getRotation(index, wraps, side, step, flips);
side = nextSide;
step = nextStep;
direction = nextDirection;
flips = nextFlips;
wraps = nextWraps;
}
angle = angle + (rotate * direction);
const dist = index ? radius : 0;
const [dx, dy] = xyForNext(dist, angle);
ox = ox + dx;
oy = oy + dy;
tris.push([ox, oy, index % 2 === 0, direction]);
}
this.originX = originX;
this.originY = originY;
this.tris = tris;
}
drawCenterpoint(t, i, context, centerX, centerY, direction) {
const radius = 4;
context.beginPath();
context.arc(centerX, centerY, radius, 0, Math.PI * 2);
context.strokeStyle = '#000';
context.lineWidth = 4;
context.stroke();
const flipColor = direction === ROTATE_CW || direction === CONTINUE_CW;
context.fillStyle = i === 0 ? '#fff' : _rgbRotate(flipColor ? 1 : 0);
context.fill();
}
drawTriangle(t, i, context, centerX, centerY, radius, pointUp) {
const hw = radius * sqrt3over2;
context.beginPath();
context.moveTo(centerX, pointUp ? centerY - radius : centerY + radius);
context.lineTo(centerX + hw, pointUp ? centerY + radius / 2 : centerY - radius / 2);
context.lineTo(centerX - hw, pointUp ? centerY + radius / 2 : centerY - radius / 2);
context.closePath();
context.lineWidth = 3;
context.strokeStyle = randomGray();
context.stroke();
}
}
document.addEventListener('DOMContentLoaded', function () {
window._app = new Application([
new Painter(function (t, w, h, canvas, context) {
context.fillStyle = '#000';
context.fillRect(0, 0, w, h);
}),
new FunkyPainter(function (t, w, h, canvas, context) {
const { radius } = this;
this.tris.forEach(([centerX, centerY, pointUp, direction], i) => {
this.drawTriangle(t, i, context, centerX, centerY, radius, pointUp);
this.drawCenterpoint(t, i, context, centerX, centerY, direction);
});
})
]);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment