Untangling ovo: collision-free pose optimization for an interlocking ring sculpture
ovo is an eight-foot sculpture of 39 interlocking wooden rings — 21 large, 18 small — woven as a three-fold-symmetric chainmaille "dragonscale pinecone." Because the rings are rigid (CNC-cut Baltic birch, not bendable wire), the design faces a question that fabric maille never asks: does a configuration exist in which every ring is correctly interlinked and no two rings occupy the same space? This page describes the geometric machinery and the optimization pipeline we used to answer it, and the dimension trade-offs it revealed.
The setup: a human-verified reference pose (hand-arranged in a Unity editor with symmetry-mirrored gizmo tooling, then relaxed under PhysX) whose topology is exactly right — 66 interlinks, each small ring linking four large rings except the bottom round's, which link two — but which retains residual interpenetration of up to ~0.9″. The optimization task: adjust each ring's rigid pose as little as possible to reach strictly positive clearance everywhere, treating the link structure as inviolable. Formally, 39 poses in SE(3) (234 DOF), a max-min clearance objective, and 66 + ~450 topological equality constraints (links that must persist, non-links that must not appear).
1. The geometric kernel
Everything rests on two primitives evaluated hundreds of thousands of times: a signed distance to a ring's solid, and a linking number between two rings.
1.1 Exact SDF for a swept rounded square
Each ring is a surface of revolution: a rounded-square cross-section (side w, corner radius r) swept along a circle of radius R. For any query point, project into cylindrical coordinates about the ring's axis: the pair (x = in-plane distance from the centerline circle, y = axial offset) locates the point in its own meridian plane, where the solid is just the 2-D cross-section. Because the solid is rotationally symmetric, the closest point on it always lies in the query point's own meridian half-plane — so the 2-D rounded-rectangle SDF evaluated in these coordinates is the exact 3-D distance:
public static float SolidSdf(in RingPose r, Vector3 p)
{
Vector3 v = p - r.Center;
float y = Vector3.Dot(v, r.Normal); // axial offset
Vector3 inPlane = v - y * r.Normal;
float x = inPlane.Length() - r.Radius; // radial offset from centerline
float qx = MathF.Abs(x) - (r.HalfWidth - r.Corner);
float qy = MathF.Abs(y) - (r.HalfThick - r.Corner);
float outside = MathF.Sqrt(Max(qx,0)*Max(qx,0) + Max(qy,0)*Max(qy,0));
float inside = MathF.Min(MathF.Max(qx, qy), 0f);
return outside + inside - r.Corner; // exact signed distance
}
Pair clearance is then computed by sampling points on ring A's true surface (cross-section boundary points spaced uniformly by perimeter — flat faces and corner arcs — swept along the centerline) and taking the minimum of B's SDF over them, plus the symmetric pass. A negative result certifies real penetration (the samples lie exactly on A's surface); a positive result under-reports clearance by at most the sampling pitch. This matters: an earlier attempt used a support-function/SAT-style bound, which turned out to produce false penetrations for linked rings meeting at steep dihedral angles — a lower bound on the separating-axis gap is not a distance. Surface-samples-vs-exact-SDF replaced it.
1.2 Topology as a computable invariant
"Ring A is threaded through ring B" is a topological statement, and it has a cheap numeric test: the linking number. We approximate A's centerline as a polyline and count signed crossings through the flat disk spanned by B's centerline. Net ±1 means simply linked; net 0 with two crossings means A pokes through the hole and back out — not linked (it can slide free). This distinction bit us early: many "plausible" arrangements pass rings through each other's apertures without actually linking them.
public static int LinkingNumber(in RingPose a, in RingPose b, int segments = 180)
{
int net = 0;
Vector3 prev = a.PointAt(0);
float prevSide = Vector3.Dot(prev - b.Center, b.Normal);
for (int i = 1; i <= segments; i++)
{
Vector3 pt = a.PointAt(i * 2f * MathF.PI / segments);
float side = Vector3.Dot(pt - b.Center, b.Normal);
if ((prevSide < 0) != (side < 0)) // crossed B's plane
{
Vector3 q = prev + prevSide / (prevSide - side) * (pt - prev);
if (Vector3.DistanceSquared(q, b.Center) <= b.Radius * b.Radius)
net += side > 0 ? 1 : -1; // ... inside the disk
}
prev = pt; prevSide = side;
}
return net; // 0 = unlinked, ±1 = simply linked
}
2. The optimizer: constrained Gauss-Seidel pose polishing
Attempts to search for the layout globally — parametric lattice families, grid search plus pattern descent, physics-based relaxation — all got close but were beaten by a human with symmetry-mirroring editor tooling. The final pipeline embraces that: the hand pose supplies the topology and the basin; the optimizer only restores feasibility. That reframing makes the problem tractable:
- Objective (soft): every pair should reach clearance margin
m = 0.1″. Energy
E = Σpairs max(0, m − cij)². - Constraints (hard): each candidate move is rejected outright if any of the 66 links would break, or any new link would form. Topology is never traded against penetration.
- Locality: a ring interacts with ~20 of the ~500 near pairs. Moving ring i only changes its own pairs' terms, so per-ring improvements monotonically decrease the global energy — classic block-coordinate (Gauss-Seidel) descent, worst-ring-first, with a 6-DOF pattern search (±x/y/z translation, ±rotations; steps annealed 0.15″ → 0.015″, 1.5° → 0.15°).
2.1 Pitfall: mixed evaluation fidelity paralyzes descent
Clearance evaluation cost scales with surface-sampling density, so an adaptive scheme is tempting: sample coarsely for far pairs, finely near contact. Our first version chose fidelity per evaluation — and the optimizer froze. Finer sampling finds deeper minima, so candidates (evaluated fine) systematically scored worse than the stale coarse baselines they were compared against; almost no move ever "improved." The fix is to make fidelity a sticky property of the pair: once a pair is ever observed near contact it is always evaluated fine, so every comparison is like-for-like. Descent resumed immediately.
// fidelity belongs to the PAIR, not the evaluation
float Clear(in RingPose a, in RingPose b, Pair q) =>
SolidClearance(a, b, q.Fine ? 192 : 96, q.Fine ? 24 : 16);
// promotion is one-way, applied when a committed value crosses the threshold
if (!q.Fine && value < Margin + 0.1f) { q.Fine = true; value = /* re-eval fine */; }
2.2 Pitfall: sum objectives sacrifice the worst pair
Halving the total energy is not the goal — the worst pair is. And a sum objective will happily deepen the single worst overlap by 0.15″ to relieve twenty shallow ones (we watched it do exactly that). The counter-measure is a maximin rescue phase: repeatedly select the globally worst pair and jointly pattern-search both of its rings against the minimum clearance over the union of their pair neighborhoods — accepting only moves that raise that local floor, with the same hard topology constraints. This lifted the worst pair by ~0.4″ beyond where energy descent stalled.
2.3 Collective moves as a diagnostic
Block-coordinate descent cannot separate two rings that are each pinned by their neighbors, so we also implemented collective moves: scaling all ring centers about the centroid (uniformly, radially, or vertically) while keeping each ring rigid — a 1-DOF move that relieves every contact at once, checked against all constraints. Instructively, in the final runs every collective expansion was rejected: the interlinks are already at full extension, so inflating the structure tightens linked pairs exactly as fast as it separates touching ones. That rejection is itself the diagnosis: the weave has no slack to give.
3. Verification, and the bug that no geometric check could see
Analytic geometry code wants an independent referee. Ours is the physics engine: each ring gets a
compound collider of 60 convex hull slices whose vertices lie exactly on the true
(roundover-included) surface, and PhysX's ComputePenetration is run over all collider
pairs. On the reference pose the two pipelines agreed to within ~10% on both the count of
penetrating pairs (134 vs 136) and the worst depth (22.3 mm vs 19.8 mm) — strong
evidence neither is hallucinating.
That referee earned its keep. The polisher's exported poses initially reproduced garbage
in Unity — a third of the links gone, half-meter overlaps — while every internal check was
green. The cause: the rotation moves rebuilt a frame vector as V = N × U
where the serialization convention required V = U × N. A sign flip on V is
invisible to every geometric test we run — rings are axisymmetric solids, and
neither the SDF nor the linking number depends on V's handedness — but it turns the frame
matrix improper (det = −1), and quaternion extraction from an improper rotation matrix
produces garbage. Only the end-to-end re-import test could catch it.
4. Results: the feasibility frontier
At the designed cross-section (4.844″ square, 0.75″ roundover) the answer is a confident no: after full optimization the fifteen worst pairs all sit within 0.04″ of one another around −0.5″, concentrated in the equatorial rounds — a uniform volumetric over-packing, not a few fixable pinch points. So the useful question becomes: what is the cheapest dimension change that makes this exact pose and topology feasible? Two levers, both preserving ring inner diameters (and therefore the weave): trimming the cross-section width, and increasing the corner roundover.
| Cross-section × roundover | Worst clearance | Penetrating pairs | Verdict |
|---|---|---|---|
| 4.844″ × 0.75″ (designed) | −0.58″ | ~130 | infeasible |
| 4.844″ × 1.00″ | −0.51″ | ~90 | infeasible |
| 4.844″ × 1.25″ | −0.26″ | ~50 | infeasible |
| 4.500″ × 0.75″ | −0.08″ | ~14 | infeasible (barely) |
| 4.375″ × 0.75″ | 0.00″ | 0 | feasible, zero margin |
| 4.600″ × 1.25″ | +0.045″ | 0 | feasible with margin |
The roundover column tells a mechanical story that matches hands-on experience assembling the 3-D printed prototypes: most contacts in this weave are corner-to-corner or corner-to-face, so rounding the corners (0.75″ → 1.25″ bought 0.32″ of worst-case clearance on its own) is nearly as valuable as trimming the whole section — but the last quarter-inch of interference is face-to-face at the equator, which only width can buy. The winning combination keeps 95% of the designed section width, and its optimized pose drifts at most 0.52″ from the human reference. Every claim above is triple-checked: the analytic pipeline, an independent re-read of the serialized result, and PhysX all agree the final configuration has all 66 links and no contact deeper than measurement noise.
Takeaways
- Human spatial reasoning beat global search for finding the configuration; the optimizer's value was certifying and repairing it. Design tools should plan for that division of labor.
- Topological constraints (linking numbers) are cheap to evaluate and make excellent hard constraints — never trade them into a penalty term.
- Exactness matters at the margins: a "conservative bound" that can report false penetrations will silently redefine your feasible set.
- Keep evaluation fidelity consistent within any comparison an optimizer makes, or descent dies quietly.
- Sum energies need a maximin phase; the worst constraint is the product.
- Verify across an independent implementation and across the serialization boundary — symmetric objects hide handedness bugs from every internal check.