Skip to content

Instantly share code, notes, and snippets.

@veltman
Last active June 21, 2017 11:54
Show Gist options
  • Save veltman/04dc09a2fd164da21f34c5301538cb0e to your computer and use it in GitHub Desktop.
Save veltman/04dc09a2fd164da21f34c5301538cb0e to your computer and use it in GitHub Desktop.
Stippling
height: 400
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
</style>
</head>
<body>
<canvas width="960" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
img = new Image(),
width = 320,
height = 400,
numSites = 2000,
capacity = 15,
iterations = 0,
numPoints = numSites * capacity,
points = new Float32Array(numPoints * 3),
sites = new Float32Array(numSites * 2),
assignments = new Uint32Array(numPoints),
bisect = d3.bisector(function(d) { return d.diff; }).left;
img.onload = initialize;
img.src = "obama.png";
function initialize() {
context.drawImage(img, 0, 0, width, height);
generatePoints(context.getImageData(0, 0, width, height).data);
generateSites();
initializeAssignments();
draw();
requestAnimationFrame(update);
}
function update() {
var stable = true;
for (var i = 0; i < numSites - 1; i++) {
for (var j = i + 1; j < numSites; j++) {
if (swapPointsBetween(i, j)) {
stable = false;
}
}
}
iterations++;
draw();
if (!stable) {
for (var i = 0; i < numSites; i++) {
recenter(i);
}
requestAnimationFrame(update);
}
}
function draw() {
var x,
y,
pi;
context.clearRect(width, 0, width * 2, height);
for (var i = 0; i < numSites; i++) {
context.fillStyle = d3.schemeCategory20[i % 20];
context.beginPath();
for (var p = 0; p < capacity; p++) {
pi = assignments[i * capacity + p];
x = points[pi * 3] + width;
y = points[pi * 3 + 1];
context.moveTo(x, y);
context.arc(x, y, 1, 0, 2 * Math.PI);
}
context.fill();
context.fillStyle = "#444";
context.beginPath();
x = sites[i * 2] + width * 2;
y = sites[i * 2 + 1];
context.moveTo(x, y);
context.arc(x, y, 1.5, 0, 2 * Math.PI);
context.fill();
}
}
function recenter(si) {
var pi,
x = 0,
y = 0;
for (var i = 0; i < capacity; i++) {
pi = assignments[si * capacity + i];
x += points[pi * 3];
y += points[pi * 3 + 1];
}
sites[si * 2] = x / capacity;
sites[si * 2 + 1] = y / capacity;
}
function swapPointsBetween(si, sj) {
var stable = true,
hi = [],
hj = [],
pi,
pj,
diff,
i;
for (i = 0; i < capacity; i++) {
pi = assignments[si * capacity + i];
diff = points[pi * 3 + 2] - distanceBetweenSiteAndPoint(sj, pi);
if (diff > 0) {
hi.splice(bisect(hi, { diff: diff }), 0, {
assignmentIndex: si * capacity + i,
distanceIndex: pi * 3 + 2,
diff: diff
});
}
pj = assignments[sj * capacity + i];
diff = points[pj * 3 + 2] - distanceBetweenSiteAndPoint(si, pj);
if (diff > 0) {
hj.splice(bisect(hj, { diff: diff }), 0, {
assignmentIndex: sj * capacity + i,
distanceIndex: pj * 3 + 2,
diff: diff
});
}
}
while (hi.length && hj.length) {
swapPoints(hi.pop(), hj.pop());
stable = false;
}
return !stable;
}
function swapPoints(fr, to) {
var swap = assignments[fr.assignmentIndex];
points[fr.distanceIndex] -= fr.diff;
points[to.distanceIndex] -= to.diff;
assignments[fr.assignmentIndex] = assignments[to.assignmentIndex];
assignments[to.assignmentIndex] = swap;
}
function initializeAssignments() {
var si;
for (var i = 0; i < numPoints; i++) {
si = Math.floor(i / capacity);
assignments[i] = i;
points[i * 3 + 2] = distanceBetweenSiteAndPoint(si, i);
}
}
function generatePoints(density) {
var x,
y;
for (var i = 0; i < numPoints; i++) {
x = null;
while (x === null) {
x = Math.random() * width,
y = Math.random() * height;
if (Math.random() > density[4 * (width * Math.floor(y) + Math.floor(x))] / 255) {
points[i * 3] = x;
points[i * 3 + 1] = y;
} else {
x = null;
}
}
}
}
function generateSites() {
for (var i = 0; i < numSites; i++) {
sites[i * 2] = Math.random() * width;
sites[i * 2 + 1] = Math.random() * height;
}
}
function distanceBetweenSiteAndPoint(si, pi) {
var dx = (points[pi * 3] - sites[si * 2]),
dy = (points[pi * 3 + 1] - sites[si * 2 + 1]);
return Math.sqrt(dx * dx + dy * dy);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment