← Portfolio

Voronoi Island Map

Why

I'd read the redblobgames "polygon map generation" article a while back and wanted to actually build one myself — but rather than reaching for an existing Voronoi library and calling it done, I wanted to implement Fortune's algorithm from scratch and make the sweep-line itself the centerpiece: watching the beachline of parabolic arcs grow, meet, and resolve into the finished diagram in real time, not just seeing the final result pop in.

About

A Voronoi diagram partitions a set of points ("sites") into regions — every location in the plane belongs to whichever site is nearest. There's more than one way to compute one, so this tool lets you pick between three independent implementations and compare how each one gets there:

Fortune's sweep line computes the diagram in O(n log n) by sweeping a horizontal line down the plane and maintaining a "beachline": a sequence of parabolic arcs, one per site the sweep has already passed, each arc being every point currently exactly as close to its site as to the sweep line itself. The onlyO(n log n) approach of the three below.

Brute-force half-plane intersection is the simplest possible correct approach: build each site's cell directly by intersecting the bounding box with every other site's perpendicular-bisector half-plane, one at a time. No sweep, no event queue — just direct geometry, at the cost of being O(n²)-ish overall.

Bowyer-Watson builds the diagram indirectly: it incrementally constructs the Delaunay triangulation of the sites (inserting one point at a time and re-triangulating), then takes its dual — each Delaunay edge's two adjacent triangles' circumcenters become a Voronoi edge. Every decision reduces to one circumcircle-vs-point test.

As with the rope bridge tool, all three live in one standalone, fully unit-tested package (@jsfc/voronoi) with no UI dependencies — each computes both a finished diagram and a complete, replayable trace of its own execution. The app you see below never re-runs any algorithm logic; it only replays whichever trace was recorded.

Conclusion

The outcome is this generator: pick a width, height, point count, and algorithm, and watch each one's own distinct animation — a sweep line and growing beachline, cells popping in one at a time, or a triangulation growing and re-triangulating — settle into a finished island, land and sea assigned by a simple jittered radial rule. Shipped as its own federated micro-frontend, the same Module Federation setup the rest of this site's portfolio pieces use.

Loading voronoi-map...

The maths
SymbolMeaning
P, QTwo sites (foci) being compared
lyThe sweep line's current y position (the directrix)
A, B, CThree sites forming a candidate circle event
W, HMap width, height
θAngle from the map center, used by the island rule

Beachline arcs. Each arc is a parabola with its site as focus and the sweep line as directrix — the locus of points equidistant from both: y(x) = (P.y + ly)/2 − (x − P.x)²/(2·(ly − P.y)). A breakpoint between two adjacent arcs is wherever their parabolas cross; solving y_P(x) = y_Q(x) gives a quadratic in x with (generally) two roots — only one is the real breakpoint between two specific adjacent arcs, decided by checking which site is actually lower (closer to the sweep line) immediately either side of each candidate.

Site events. When the sweep reaches a new site, it's inserted into whichever arc currently sits above it, splitting that arc in two around a zero-width sliver for the new site — the start of two new edges tracing the perpendicular bisector between the split site and the new one.

Circle events. Three consecutive arcs' sites A, B, C define a circumcircle; if the sweep reaches that circle's lowest point (center.y + radius) before the middle arc B would otherwise widen further, B's arc has shrunk to nothing — it's removed, and its two bordering edges are finalized at the circumcircle's center, the true Voronoi vertex where three cells meet.

Half-plane intersection. The set of points closer to site P than to site Q is a half-plane: the perpendicular bisector of P and Q, kept on P's side. Intersecting the bounding box with that half-plane for every other site, one at a time, leaves exactly P's cell — direct and always correct, just quadratic in the number of sites.

Delaunay/Voronoi duality. Bowyer-Watson builds the Delaunay triangulation by inserting sites one at a time: for each new site, every triangle whose circumcircle contains it is invalid and removed, and the resulting hole is re-triangulated by connecting the new site to the hole's boundary. Once built, each Delaunay edge's two adjacent triangles' circumcenters become the endpoints of one Voronoi edge — no ambiguity, since a triangle's circumcenter is a single well-defined point, not a quadratic with two roots to choose between.

Island shape. Not a physical simulation — a site is land if its distance from the map center is within a jittered threshold: a handful of random radius multipliers at evenly-spaced angles around a full circle, linearly interpolated between the two bracketing angles at any given θ, so the coastline is an irregular closed curve rather than a perfect circle without needing a noise function.

Changelog
VersionDateChanges
1.2.02026-07-28Fixed Fortune's sweep line: the beachline lookup, breakpoint-direction convention, and final open-edge resolution each had a distinct bug (see the comment atop @jsfc/voronoi's fortune.ts) — all three algorithms now verified correct against thousands of randomized configurations, and Fortune's is back to being the default.
1.1.02026-07-27Added an algorithm switcher — brute-force half-plane intersection and Bowyer-Watson incremental Delaunay triangulation, each with its own distinct animation, alongside Fortune's sweep line (at the time, a known-buggy experimental reference implementation).
1.0.02026-07-27Initial release — a from-scratch Fortune's-algorithm implementation (@jsfc/voronoi) with a recorded, replayable animation trace, an animated sweep-line/beachline canvas view, and a simple jittered-radial island shape.