Lipschitz twist extrude#88
Open
snowbldr wants to merge 2 commits into
Open
Conversation
The twist mapping rotates (x, y) by θ = k·z where k = twist/height. Its
Jacobian has maximum singular value σ_max = √(1 + k²r²) with
r² = x² + y². The 2D thread SDF returned by Evaluate is in the
un-twisted frame, so it overestimates true 3D distance by up to σ_max.
Without correction, the octree marching-cubes renderer's |sdf(center)|
≥ half-diagonal pruning skips cubes that contain surface, producing
holes.
ExtrudeSDF3 grows an invStretch field set at construction:
k = twist/height
rMax² = max(|bb.Min.X|, |bb.Max.X|)² + max(|bb.Min.Y|, |bb.Max.Y|)²
invStretch = 1 / √(1 + k²·rMax²)
All surfaces of the extruded shape lie within the 2D bounding box, so
r ≤ rMax and a single global constant is conservative everywhere.
Evaluate multiplies its result by invStretch to scale the over-stated
distance back to a valid Lipschitz-1 estimator. Plain Extrude3D and the
scale variants set invStretch = 1 (no correction); their separate
Lipschitz issues are out of scope for this PR (Scale/ScaleTwist need a
2D-Jacobian correction tracked elsewhere).
render/twist_extrude_test.go: watertight test sweeping twist from 30°
to 2 full turns over both square and thin-rect 2D profiles, asserting
zero boundary edges from the octree renderer at 80 cells.
…figs Cover the full failure space for the σ_max bound: 7 shape profiles (square, thin rect, circle, triangle, rounded square — and factories for negative-quadrant / off-axis cases left commented out pending the sibling bbox-fix PR) × 12 twist configurations (zero, fractions of a turn, full turn, multiple turns, negative, varied heights). Plus a high-resolution stress pass at cells=200 on the worst-case configurations that's gated by -short. Each combination asserts zero boundary edges from the octree. Pure twist=0 is also pinned to verify k=0 is a no-op.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix TwistExtrude3D holes during octree rendering
Fixes one of the bugs in #85.
The bug
TwistExtrude3Devaluates by un-twisting the 3D query point back into the2D thread frame and asking the 2D SDF for a distance. The un-twist mapping
rotates
(x, y)byθ = k·zwherek = twist/height; its Jacobian'smaximum singular value is
so a unit step in 3D corresponds to up to σ_max in the un-twisted 2D space.
The 2D SDF therefore returns a value larger than the true 3D distance by
that factor. The octree marching-cubes renderer prunes cubes via
|sdf(center)| ≥ half-diagonal, and an over-stated distance causes it toskip cubes that actually contain surface — holes.
The uniform renderer is unaffected because it evaluates every cube
unconditionally; the bug only shows up under octree.
The fix
ExtrudeSDF3grows aninvStretch float64field set at construction.For
TwistExtrude3D:All surfaces of the extruded shape sit inside the 2D bounding box, so
r ≤ rMaxeverywhere on the surface and a single global constantinvStretchis a sound conservative bound.Evaluatemultiplies itsresult by
invStretchso the SDF stays a valid Lipschitz-1 estimatorthe renderer can prune against.
Extrude3D,ScaleExtrude3D, andScaleTwistExtrude3DinitializeinvStretch = 1(no correction). PlainExtrude3Dis already1-Lipschitz;
ScaleExtrude3D/ScaleTwistExtrude3Dhave their owndistinct Jacobian shapes and need their own correction (separate PR).
Tests
render/twist_extrude_test.gosweeps twist from 30° through 2 full turnsover both
Box2D(4×4)andBox2D(8×1)2D profiles, asserting zeroboundary edges from the octree renderer at 80 cells. All pass on macOS.
Architecture-specific note
This is the same family of bug as the high-taper screw rendering issue
fixed in #84 — a non-1-Lipschitz SDF that the octree's
isEmptypruning rule wrongly trusts. Borderline configurations may render
holes on x86_64 (FMA / FTZ rounding differences) but not on Apple
Silicon. The conservative
invStretchconstant has plenty of margineither way.