Point Relax
Soft-repulsion relaxation for points — each point pushes away from neighbors in proportion to overlap, weighted by per-point scale. Iteratively settles into "loosely packed circles" where bigger points get more breathing room and smaller points fill the gaps.
Category: Point Ops Menu path: Point Ops > Point Relax
Ports
| Port | Type | Direction | Description |
|---|---|---|---|
points_in | points | input | Input points (typically post-PointAttributes with per-point scale) |
out | points | output | Points with adjusted positions; attributes and PointIds preserved |
Parameters
| Param | Type | Default | Description |
|---|---|---|---|
radius | scalar | 30 | Base radius for a unit-scale point. Match downstream DrawPoints.pointSize for strict no-overlap. |
iterations | scalar | 8 | Relaxation passes. Higher = more settled; cost is O(N²) per iteration. |
stiffness | scalar | 0.5 | 0–1, how aggressively each iteration resolves overlap. 1.0 may oscillate; 0.5 is the smooth default. |
scaleAttribute | string | scale | Per-point attribute name that multiplies the base radius. Default matches PointAttributes(target=Scale). |
boundsMode | string | Free | Free (drift freely) / Clamp (pinned inside the input's AABB). |
How it works
For every iteration:
- For each pair of points (i, j), compute the distance between them and their target distance —
target = radius * (scale[i] + scale[j]). - If distance < target, push them apart along the connecting line by half the overlap ×
stiffness. - If two points are coincident (distance ≈ 0), nudge them apart along a deterministic axis derived from their indices, so coincident clusters don't all shoot the same direction.
After all pairs in an iteration are evaluated, all forces are applied at once (so the order of pair evaluation doesn't bias the result), with optional AABB clamping.
The algorithm is intentionally O(N²). For typical motion-design point counts (50–2000) it runs in microseconds to a few milliseconds even at 50+ iterations. Above 10,000 points a spatial-hash optimization would be needed; until that happens, downsample with PointDelete or sample fewer points upstream.
Usage examples
Image-driven scale with no overlap.
ImageSource → ImageSample(scalarField)
↓
Grid → PointAttributes(target=Scale, source=Field)
→ PointRelax(radius = DrawPoints.pointSize)
→ DrawPointsThe bright regions of your image grow larger points; PointRelax pushes them apart so circles never overlap. Points in dark regions stay small and fill gaps. Set radius exactly to DrawPoints.pointSize and you get the canonical "weighted circle pack" look.
Organic clustering. Reduce radius slightly below DrawPoints' size — points overlap a bit, creating an organic "cells" feel without harsh circle boundaries.
Animated repacking. Animate the upstream scale attribute (e.g. via Time.frame driving a Math node into the field). Points smoothly migrate as their target sizes change. Increase iterations for tighter tracking, decrease for visible "settling lag".
Constrained to region. Set boundsMode = Clamp to pack points strictly inside the input's bounding box — useful for "fill exactly this rectangle with non-overlapping circles" effects. Points stack against the boundary instead of drifting outside.
Tips
- Match
radiustoDrawPoints.pointSizefor strict no-overlap. Multiplyradiusby 1.2–1.5 for breathing room between points; reduce to 0.7–0.9 to allow overlap that the eye reads as denser packing. - Animation needs more iterations. A static layout settles in 5–8 iterations; an animated scale field (image-driven) wants 12–20 to track smoothly without visible "popping" as scales change between frames.
stiffnesscontrols the visual settling rate. 1.0 resolves overlap immediately each iteration (looks twitchy with animated input); 0.3–0.5 produces the soft, organic settling that reads best for motion design.- Combine with PointAdvect for relaxed flow — relax first, then advect. Relaxing AFTER advection fights the flow's natural direction and looks chunky.
- Pre-relax bias. If your input grid is too uniform, the relaxation barely moves it. Drop a small Jitter (e.g. PointDeform with low-amplitude noise) before PointRelax to break symmetry — relaxation then settles into something more organic-looking.
Performance
| Point count | Iterations | Cost (typical) |
|---|---|---|
| 100 | 8 | < 0.5 ms |
| 500 | 8 | ~5 ms |
| 1000 | 8 | ~20 ms |
| 5000 | 8 | ~500 ms |
| 10000+ | — | use spatial hashing or downsample |
Quality scaling doesn't affect this — it runs at full point count regardless of viewport quality. For animated point fields, drop a FileCache after PointRelax to avoid re-running on playback.
Related nodes
- PointAttributes — provides the per-point
scaleattribute that PointRelax weights against. - DrawPoints — renders the relaxed points; match its
pointSizeto PointRelax'sradiusfor no-overlap. - PointAdvect — flow-based motion. Relax before, not after.
- PointDeform — field-driven displacement. Pair upstream of PointRelax to break grid symmetry.
- PointScatter — random distribution. Often a better starting point than Grid for organic relaxation.