function poissonDisk() {
let firstCircle = Circle.random();
let candidates = [firstCircle];
let allCircles = [firstCircle];
let quads = quadtree();
let simplex = new SimplexNoise(randomSeed);
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);
};
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;
};
let nearbyCircles = (circle, radius = searchRadius) => {
return search(quads, circle.x, circle.y, radius);
};
let tooClose = (candidate, radius) => {
for (let circle of nearbyCircles(candidate, radius)) {
if (candidate.tooClose(circle, radius)) return true;
}
return false;
};
let generateCandidate = (active) => {
let radius = searchRadius * ((simplex.noise3D(active.x / width * spacing, active.y / height * spacing, time) / 2 + 0.5) * radiusMultiplier + radiusShift);
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;
}
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;
}