Last active
January 21, 2019 17:41
-
-
Save keville/1ebf22ed453166f96dd45fae333587ae to your computer and use it in GitHub Desktop.
ToyBox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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