Istari Arts

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.

ring axis centerline (dist R) x (radial) y (axial) p SDF(p) > 0
Fig. 1 — Rotational symmetry reduces the 3-D distance query to a 2-D rounded-rectangle SDF in the query point's own meridian plane: (x, y) = (distance from the centerline circle, axial offset).

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
}
+1 linked: one crossing inside, return path outside the disk +1 −1 poke-through: net 0, not linked — slides free
Fig. 2 — Linking number = net signed crossings of one centerline through the disk spanned by the other. The poke-through case fooled several early automated layouts: geometrically intertwined, topologically free.

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°).
i moving ring i re-evaluates only its own pairs (blue)
Fig. 3 — Energy is a sum over pairs; a ring's move touches only the terms it participates in. Per-ring pattern-search improvements therefore never increase the global energy, and one polish round costs ~40 pair evaluations per ring.

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.

Lesson for anyone optimizing poses of symmetric objects: your internal invariants can be blind to exactly the degrees of freedom your serialization depends on. Round-trip the boundary, always.

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.

Worst pair clearance after optimization (inches) −0.6 −0.4 −0.2 0 +0.1 4.844" × 0.75" roundover — worst clearance −0.58" (infeasible) infeasible −0.58 4.844" × 1.00" roundover — worst clearance −0.51" (infeasible) infeasible −0.51 4.844" × 1.25" roundover — worst clearance −0.26" (infeasible) infeasible −0.26 4.500" × 0.75" roundover — worst clearance −0.08" (infeasible) −0.08 infeasible 4.375" × 0.75" roundover — worst clearance 0.00" (feasible, grazing contact) 0.00 feasible (grazing) 4.600" × 1.25" roundover — worst clearance +0.045" (feasible with margin) +0.045 feasible 4.844″ × r1.00″ 4.844″ × r1.25″ 4.500″ × r0.75″ 4.375″ × r0.75″ 4.600″ × r1.25″ 4.844″ × r0.75″
Fig. 4 — Worst-pair clearance after full optimization, per cross-section width × corner roundover (ring inner diameters fixed). Red = infeasible (residual interpenetration), green = feasible. The designed 4.844″/0.75″ section is ~0.5″ over-packed; 4.6″ with a 1.25″ roundover clears with genuine margin.
Cross-section × roundoverWorst clearancePenetrating pairsVerdict
4.844″ × 0.75″ (designed)−0.58″~130infeasible
4.844″ × 1.00″−0.51″~90infeasible
4.844″ × 1.25″−0.26″~50infeasible
4.500″ × 0.75″−0.08″~14infeasible (barely)
4.375″ × 0.75″0.00″0feasible, zero margin
4.600″ × 1.25″+0.045″0feasible 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.

Try the interactive simulator · ← Back to Istari Arts