Published
Edited
Aug 8, 2018
1 fork
15 stars
Insert cell
Insert cell
Draw(poissonDisk(), radius, width, height) // press shift-enter on me
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function poissonDisk() {
let firstCircle = Circle.random();
let candidates = [firstCircle];
let allCircles = [firstCircle];
let quads = quadtree();
let simplex = new SimplexNoise(randomSeed);
// Adapted from https://bl.ocks.org/wonga00/3987b9736d5077501ef5ee0409ea93c1
let inCircle = (x, y, cx, cy, radius) => {
return (x - cx) * (x - cx) + (y - cy) * (y - cy) < radius * radius;
};

let rectCircleIntersect = (rx, ry, rwidth, rheight, cx, cy, radius) => {
let dx = cx - Math.max(rx, Math.min(cx, rx + rwidth));
let dy = cy - Math.max(ry, Math.min(cy, ry + rheight));
return (dx * dx + dy * dy) < (radius * radius);
};
// Find the nodes within the specified circle.
let search = (quadtree, cx, cy, radius) => {
let results = [];
quadtree.visit(function(node, x1, y1, x2, y2) {
if (!node.length) {
do {
var d = node.data;
if (inCircle(d[0], d[1], cx, cy, radius)) {
results.push({
x: d[0],
y: d[1],
});
}
} while (node = node.next);
}

return !rectCircleIntersect(x1, y1, x2 - x1, y2 - y1, cx, cy, radius);
});
return results;
};
// A generator for nearby circles using the quadtree.
let nearbyCircles = (circle, radius = searchRadius) => {
return search(quads, circle.x, circle.y, radius);
};
// Checks all created circles to see if the candidate is too close.
let tooClose = (candidate, radius) => {
for (let circle of nearbyCircles(candidate, radius)) {
if (candidate.tooClose(circle, radius)) return true;
}
return false;
};
// Try to generate a candidate and return it (null on fail).
let generateCandidate = (active) => {
let radius = searchRadius * ((simplex.noise3D(active.x / width * spacing, active.y / height * spacing, time) / 2 + 0.5) * radiusMultiplier + radiusShift);
// Generate random circles filled disc from radius r to 2r
for (var i = 0; i < searchAttempts; i++) {
let candidate = active.candidate(radius);
if (candidate.inBounds() && !tooClose(candidate, radius)) return candidate;
}
active.radius = radius;
return null;
}
// Add circle to the results and candidates.
let addCircle = (circle) => {
candidates.push(circle);
allCircles.push(circle);
quads.add([circle.x, circle.y]);
};
while (candidates.length > 0) {
let active = candidates[candidates.length - 1];
let candidate = generateCandidate(active);
if (candidate == null) {
candidates.pop();
} else {
addCircle(candidate);
}
}
return allCircles;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more