fix Screw3D Lipschitz under-correction at high taper#87
Open
snowbldr wants to merge 2 commits into
Open
Conversation
ScrewSDF3.Evaluate computes rEff = max(r - threadDepth, r/2) where r is the query point's radial distance, then uses rEff in the Lipschitz σ_max formula. For a cylindrical screw the closest surface to any query is near r_query so this is a tight bound, but for a tapered screw the cone narrows along z and the closest surface to a query can sit at much smaller r — e.g. a query at the wide end can have its closest surface at the cone's narrow end if the cube spans z. Without clamping rEff at the global minimum surface r, σ_max gets under-estimated for those queries, the SDF over-reports distance, and the octree's isEmpty check skips cubes that contain surface. The result is borderline floating-point: holes appear on Linux and Windows but not on macOS for the same configurations (jasonh@eccles reported 4 of 41 configs failing in examples/screw_assortment at cells=300: taper_45 (32 boundary edges) taper_30_coarse (142) taper_30_buttress (68) taper_30_left_buttress (68)) Fix: at construction compute rSurfaceMin = bb.Max.Y − length·tan(taper) − threadDepth (the smallest possible surface r anywhere on the screw), floored at 5% of the crest radius so σ_max stays bounded when the cone pinches to or past the axis (e.g. taper=45° length=20 R=5 has a cone tip at z=5). In Evaluate, clamp rEff to rSurfaceMin from above before the σ_max calculation. For cylindrical screws this is a no-op for queries inside the screw radius and a small extra correction outside. render/screw_test.go: pin the 4 failing configs as a high-resolution watertight test at cells=300 to match the assortment example so future regressions surface.
Was 4 originally-reported failing configs; now 14 covering the wider
hole-prone space:
- the 4 originals (taper 30/45 × iso/buttress)
- taper sweep at 35°, 40°, 45° on iso and buttress
- high-taper multi-start (4 and 8 starts)
- alternative profiles at high taper (acme, plastic-buttress, iso-int)
- longer cone (length=40) — narrows further
- fine pitch (p=1) at high taper — small rEff at the tip stresses
the rSurfaceMin floor
All assert zero boundary edges from the octree at cells=300 (matching
the assortment example).
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 Screw3D Lipschitz under-correction at high taper
Fixes the watertight-test failures reported on
examples/screw_assortmentatcells=300for high-taper configurations:taper_45taper_30_coarsetaper_30_buttresstaper_30_left_buttressHeads-up: I couldn't reproduce locally
Every config above renders with 0 boundary edges on my machine
(macOS / Apple Silicon) at
cells=300,cells=600, and the existingunit tests at
cells=100. The failures are platform-specificfloating-point artifacts.
The most likely cause is FMA (fused multiply-add) emission differences
between architectures. The Go compiler emits
fmaddon arm64 forexpressions of the shape
a*b + c*d(one rounding step) but typicallyemits separate
mulsd; addsdon amd64 (two rounding steps). The screwσ_max formula has exactly this shape:
A 1-ulp difference in
discpropagates throughSqrt, through thedivide, and into the final reported distance. The octree's pruning rule
is
|sdf(center)| ≥ half-diagonal— exactly the kind of comparisonthat flips on 1-ulp shifts when the SDF is borderline. At high taper
the existing correction was just tight enough; either rounding
direction kept it on the right side on macOS/arm64 but pushed it off
on Linux/amd64.
Secondary suspects (less likely but worth knowing about):
the helical math they get flushed to zero on x86 by default but
preserved on ARM. Could shift values by far more than 1 ulp.
Sqrt,Atan2,Sin,Cos) is bit-for-bitdeterministic across platforms given identical inputs, so it
isn't the source — the non-determinism comes from how the compiler
generates the surrounding multiplies and adds.
The actual bug
ScrewSDF3.EvaluatecomputesrEff = max(r_query - threadDepth, r_query/2)and feeds that intoσ_max. For a cylindrical screw the closest surface to any query is
near
r_query, so this is a tight bound. For a tapered screw the conenarrows along z, and the closest surface to a query can sit at much
smaller r — a query at the wide end can have its closest surface at
the cone's narrow end if the cube spans z. The existing correction
under-estimated σ_max for those queries, the SDF over-reported
distance, the octree's
isEmptycheck skipped cubes that containsurface, and the rendered mesh had holes.
Fix
At construction, compute the smallest possible surface r anywhere on
the screw:
The 5%-of-crest floor keeps σ_max bounded when the cone pinches to
or past the axis (e.g.
taper=45° length=20 R=5has the cone tip atz=5, well inside the screw's z range).In
Evaluate, clamprEfffrom above byrSurfaceMin:For a cylindrical screw
rSurfaceMin = root radius, so the clamp isa no-op for queries inside the screw radius and a small extra
correction outside. For tapered screws it pulls the bound down to
whatever the narrowest part of the cone produces — conservative but
correct, and tight enough to absorb the 1-ulp wobble that was the
proximate cause of the platform-specific holes.
Tests
render/screw_test.gogainsTest_Screw_Watertight_HighTaperHighReswith the four reported configurations at
cells=300(matching theassortment example). Passes locally; should also pass on the
Linux/amd64 box that originally reported the failures.