Skip to content

Commit ad97730

Browse files
committed
add uniformity option
1 parent 310a84b commit ad97730

4 files changed

Lines changed: 75 additions & 13 deletions

File tree

PR.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
## Optimize Voronoi effect performance
1+
## Optimize Voronoi and add uniformity option
22

3-
Replaced Dijkstra with BFS for shortest path finding in graph traversal, since all edge weights are uniform (1).
4-
5-
**Changes:**
6-
- `src/common/Graph.js`: Added `bfsShortestPath()` - O(V+E) vs original O(V²)
7-
- `src/common/Graph.js`: Removed cache invalidation during graph construction
8-
- `src/common/voronoi.js`: Use `bfsShortestPath()`
9-
- `src/common/lindenmayer.js`: Use `bfsShortestPath()`
10-
- `src/features/shapes/tessellation_twist/TessellationTwist.js`: Use `bfsShortestPath()`
11-
- `src/features/effects/effectFactory.js`: Re-enabled Voronoi effect
3+
Replaced Dijkstra with BFS for shortest path finding in graph traversal, since all edge weights are uniform (1). Added Lloyd relaxation as a "Uniformity" slider to both Voronoi shape and effect.
124

135
**Performance improvement:**
146
~200x faster path finding (BFS O(V+E) vs old Dijkstra with O(n) priority queue)
157

8+
**Uniformity option:**
9+
- 0 (default): original chaotic/irregular cells
10+
- 5-10: cells approach uniform hexagons (honeycomb aesthetic)
11+
- Works with both voronoi and delaunay polygon types
12+
- Complements weight functions: weight controls density, uniformity controls regularity
13+
1614
**Notes:**
1715
- `dijkstraShortestPath()` kept for future weighted edge use cases
18-
- Path cache note added for future maintainers

src/common/voronoi.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ export class VoronoiMixin {
234234
voronoiMinDistance,
235235
voronoiMaxDistance,
236236
voronoiZoom,
237+
voronoiUniformity = 0,
237238
} = options
238239
const width =
239240
voronoiPlacement == "poisson disk sampling"
@@ -270,6 +271,10 @@ export class VoronoiMixin {
270271
})
271272
}
272273

274+
if (voronoiUniformity > 0) {
275+
points = this.relaxPoints(points, width, height, voronoiUniformity)
276+
}
277+
273278
return points
274279
}
275280

@@ -337,4 +342,44 @@ export class VoronoiMixin {
337342

338343
return graph.findNode(onEdge) || closestNode
339344
}
345+
346+
// Lloyd relaxation: move each point to the centroid of its Voronoi cell
347+
relaxPoints(points, width, height, iterations) {
348+
let relaxedPoints = points
349+
350+
for (let i = 0; i < iterations; i++) {
351+
const delaunay = Delaunay.from(relaxedPoints)
352+
const voronoi = delaunay.voronoi([0, 0, width, height])
353+
354+
relaxedPoints = relaxedPoints.map((point, idx) => {
355+
const cell = voronoi.cellPolygon(idx)
356+
if (!cell || cell.length < 3) return point
357+
358+
// Calculate centroid of the cell polygon
359+
let cx = 0
360+
let cy = 0
361+
let area = 0
362+
363+
for (let j = 0; j < cell.length - 1; j++) {
364+
const [x0, y0] = cell[j]
365+
const [x1, y1] = cell[j + 1]
366+
const cross = x0 * y1 - x1 * y0
367+
368+
area += cross
369+
cx += (x0 + x1) * cross
370+
cy += (y0 + y1) * cross
371+
}
372+
373+
area /= 2
374+
if (Math.abs(area) < 1e-10) return point
375+
376+
cx /= 6 * area
377+
cy /= 6 * area
378+
379+
return [cx, cy]
380+
})
381+
}
382+
383+
return relaxedPoints
384+
}
340385
}

src/features/effects/Voronoi.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export const voronoiOptions = {
2323
type: "togglebutton",
2424
choices: ["voronoi", "delaunay"],
2525
},
26+
voronoiUniformity: {
27+
title: "Uniformity",
28+
type: "slider",
29+
min: 0,
30+
max: 20,
31+
},
2632
}
2733

2834
export default class Voronoi extends Effect {
@@ -51,14 +57,16 @@ export default class Voronoi extends Effect {
5157
...super.getInitialState(),
5258
...{
5359
voronoiPolygon: "voronoi",
60+
voronoiUniformity: 0,
5461
seed: 1,
5562
},
5663
}
5764
}
5865

5966
getVertices(effect, layer, vertices) {
60-
const { seed, voronoiPolygon } = effect
67+
const { seed, voronoiPolygon, voronoiUniformity = 0 } = effect
6168
const { width, height } = dimensions(vertices)
69+
6270
this.rng = seedrandom(seed)
6371
noise.seed(seed)
6472

@@ -68,7 +76,12 @@ export default class Voronoi extends Effect {
6876
),
6977
(vertex) => vertex.toString(),
7078
)
71-
const points = this.generatePointsFromVertices(mappedVertices)
79+
let points = this.generatePointsFromVertices(mappedVertices)
80+
81+
if (voronoiUniformity > 0) {
82+
points = this.relaxPoints(points, width, height, voronoiUniformity)
83+
}
84+
7285
this.graph = this.buildGraph(points, voronoiPolygon, width, height)
7386

7487
this.vertices = []

src/features/shapes/Voronoi.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ export const voronoiOptions = {
7777
)
7878
},
7979
},
80+
voronoiUniformity: {
81+
title: "Uniformity",
82+
type: "slider",
83+
min: 0,
84+
max: 20,
85+
},
8086
seed: {
8187
title: "Random seed",
8288
min: 1,
@@ -107,6 +113,7 @@ export default class Voronoi extends Shape {
107113
voronoiMinDistance: 30,
108114
voronoiMaxDistance: 50,
109115
voronoiFrequency: 5,
116+
voronoiUniformity: 0,
110117
seed: 1,
111118
},
112119
}

0 commit comments

Comments
 (0)