Created
July 12, 2018 01:30
-
-
Save mattdesl/d2ab8b8cee617f749a25bad69c0b1182 to your computer and use it in GitHub Desktop.
paint worklet + generative art (brute force circle packing demo)
This file contains hidden or 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> | |
<div id="sketch"></div> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
background: #383838; | |
} | |
#sketch { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
--seed: 3; | |
--circle-count: 100; | |
--circle-size: 1; | |
--circle-color: #7bf5de; | |
background-image: paint(canvas-sketch); | |
} | |
</style> | |
<script> | |
if (location.protocol === 'http:' && location.hostname !== 'localhost') | |
location.protocol = 'https:'; | |
if ('paintWorklet' in CSS) { | |
CSS.paintWorklet.addModule('paintworklet.js'); | |
} else { | |
document.body.innerHTML = 'You need support for <a href="https://drafts.css-houdini.org/css-paint-api/">CSS Paint API</a> to view this demo :('; | |
} | |
</script> |
This file contains hidden or 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
// ---- Utilities | |
// It seems like Math.random() is causing trouble | |
// with paint worklets ... let's use a seeded one instead | |
const createRandom = (seed) => { | |
// From: | |
// https://gist.github.com/blixt/f17b47c62508be59987b | |
let _seed = seed % 2147483647; | |
if (_seed <= 0) _seed += 2147483646; | |
const next = () => { | |
_seed = _seed * 16807 % 2147483647; | |
return _seed; | |
}; | |
const value = () => { | |
return (next() - 1) / 2147483646; | |
}; | |
const gaussian = () => { | |
return Math.sqrt(-2.0 * Math.log(value())) * Math.cos(2.0 * Math.PI * value()) | |
}; | |
return { | |
value, | |
gaussian | |
}; | |
}; | |
const circleCollides = (a, b, padding = 0) => { | |
const radii = a.radius + b.radius + padding; | |
const radiiSq = (radii * radii); | |
const x = b.position[0] - a.position[0]; | |
const y = b.position[1] - a.position[1]; | |
const distSq = x * x + y * y; | |
return distSq <= radiiSq; | |
}; | |
// ---- Sketch / Artwork Code | |
const sketch = () => { | |
let seed, random, count; | |
const circles = []; | |
const pack = () => { | |
const darts = 500; | |
const newCircles = []; | |
const padding = 0.005; | |
// throw N darts on board | |
for (let i = 0; i < darts; i++) { | |
newCircles.push({ | |
radius: Math.abs(random.gaussian()) * 0.1, | |
position: [ random.value(), random.value() ], | |
collisions: 0 | |
}); | |
} | |
// collide each dart with all other circles | |
newCircles.forEach(circle => { | |
circles.forEach(other => { | |
const collides = circleCollides(circle, other, padding); | |
if (collides) circle.collisions++; | |
}); | |
}); | |
// find best fit | |
newCircles.sort((a, b) => a.collisions - b.collisions) | |
circles.push(newCircles[0]); | |
}; | |
const generate = (newSeed = 0, newCount = 100) => { | |
seed = newSeed; | |
count = newCount; | |
random = createRandom(seed); | |
circles.length = 0; | |
for (let i = 0; i < count; i++) { | |
pack(); | |
} | |
}; | |
generate(); | |
return { | |
properties: [ | |
'--circle-color', | |
'--circle-size', | |
'--circle-count', | |
'--seed' | |
], | |
render ({ context, width, height, css }) { | |
const color = css.get('--circle-color').toString(); | |
const size = parseFloat(css.get('--circle-size').toString()); | |
const newSeed = parseInt(css.get('--seed').toString(), 10); | |
const newCount = parseInt(css.get('--circle-count').toString(), 10); | |
if (newSeed !== seed || newCount !== count) { | |
generate(newSeed, newCount); | |
} | |
context.clearRect(0, 0, width, height); | |
context.save(); | |
const scale = Math.max(width, height); | |
context.scale(scale, scale); | |
circles.forEach(({ position, radius }) => { | |
context.beginPath(); | |
context.arc(position[0], position[1], radius * size, 0, Math.PI * 2, false); | |
context.fillStyle = color; | |
context.fill(); | |
}); | |
context.restore(); | |
} | |
}; | |
}; | |
// ---- Generic boilerplate stuff, same for each sketch | |
const run = (props, render) => { | |
class Sketch { | |
static get inputProperties () { | |
return props; | |
} | |
paint (context, geometry, css) { | |
render({ | |
context, | |
geometry, | |
width: geometry.width, | |
height: geometry.height, | |
css | |
}); | |
} | |
} | |
registerPaint('canvas-sketch', Sketch); | |
}; | |
let loader = sketch(); | |
if (typeof loader.then !== 'function') { | |
loader = Promise.resolve(loader); | |
} | |
loader.then(result => { | |
run(result.properties, result.render); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment