Skip to content

feat(extract): NURBS curve + patch surface tessellation#29

Merged
delta9000 merged 14 commits into
mainfrom
feat/nurbs-curve-patch
Jun 27, 2026
Merged

feat(extract): NURBS curve + patch surface tessellation#29
delta9000 merged 14 commits into
mainfrom
feat/nurbs-curve-patch

Conversation

@delta9000

Copy link
Copy Markdown
Owner

Implements first-party tessellation for NurbsCurve and NurbsPatchSurface, closing NRB-1 for curve + patch. Previously both were generated stubs that the extractor silently dropped.

What's in it

  • runtime/extract/NurbsEval.hpp — node-free NURBS math: Cox–de Boor basis + first-derivative basis, rational (weighted) curve/surface evaluation, clamped + periodic knot defaulting, full closed/uClosed/vClosed handling, and analytic normals via the quotient rule (A' − w'C)/w.
  • Two MeshBuilder armsNurbsCurveTopology::Lines; NurbsPatchSurfaceTriangles + analytic normals + implicit (u,v) texcoords. Registered in recognizedGeometryType().
  • Convex-hull bounds for both (control-point AABB) in GeometryBounds.hpp.
  • Docs: new ADR (first-party, not a seam — I/O-free + spec-prescribed ⇒ no genericity payoff; externalGeometryResolver stays the unrecognized-fallback for the deferred surfaces), conformance NRB-1 → fixed (NRB-3 tracks deferred swept/swung/trimmed), extract subsystem page, v1-capabilities.

Scope

Curve + patch only. Deferred to follow-ups: NurbsTrimmedSurface, swept/swung, NURBS interpolators, authored NurbsTextureCoordinate, 2D geometry.

Verification

  • All NURBS math machine-verified against a computer-algebra system (exact circle, Bézier reduction, partition-of-unity, the first-derivative basis vs symbolic differentiation, the periodic closed construction) — see the spec's provenance section.
  • New doctest suites: pure-math (nurbs_eval_test) + integration (mesh_builder_nurbs_test); recognizedGeometryType oracle; unrecognized-specimen moved to NurbsTrimmedSurface.
  • mise run ci green (ci-preset build 111/111 with -Werror + per-header isolation; Python suite 305 passed/1 skipped; golden/conformance/coverage/cli gates pass).
  • Visually confirmed end-to-end through the headless CPU rasterizer (bicubic hill, hyperbolic-paraboloid saddle, closed surface-of-revolution, NurbsCurve helix) — smooth specular highlights confirm the analytic normals; closed surface shows no seam.

Spec: docs/superpowers/specs/2026-06-27-nurbs-curve-patch-design.md. Plan: docs/superpowers/plans/2026-06-27-nurbs-curve-patch.md.

Note: the headless renderer needed an unrelated ADR-0039 namespace fix to build (it's out-of-CI); that's a separate PR (#28).

delta9000 added 14 commits June 27, 2026 16:21
First-party NurbsEval math unit + two MeshBuilder arms (curve→lines,
patch→triangles with analytic normals), full periodic closed handling,
convex-hull bounds. Closes NRB-1 for curve+patch; trimmed/swept/swung,
interpolators, and 2D geometry deferred to follow-ups. Includes the
'why not a seam' rationale (I/O-free + spec-prescribed ⇒ no genericity
payoff; externalGeometryResolver stays as the override).
Math-unit-first: open/closed curves -> open/closed surfaces with analytic
normals (all formulas machine-verified) -> MeshBuilder arms -> bounds ->
specimen swap -> docs -> CI. Each task ships verified golden values.
…ot double)

Caught by adversarial workflow execution: the golden asserted |x^2+y^2-1|<1e-9
on float-stored points whose achievable error is ~1 ulp (6e-8). Math is exact
(double accumulation, float output); only the test bound was impossible.
Caught by adversarial workflow execution: comparing the chord leaving the seam
to the chord arriving caps the dot at ~0.923 (chords straddle the true tangent
by the curvature), so >0.99 is impossible even for a correct C1 curve. Replaced
with: seam chord-turning == diametrically-opposite vertex's (verified == 1e-16);
a C0 kink would break that equality. Curve is genuinely C1.
- ci-preset -Werror=narrowing: finite-difference test used double h -> float
  brace-init; make h float (blocking; dev preset does not -Werror).
- review (medium): prepareSurface wrapped closed surfaces unconditionally while
  prepareCurve guards on endsDiffer. Add the coincident-boundary guard so an
  already-closed control net is not duplicated into zero-area sliver triangles;
  + regression test (already-closed net + uClosed == unwrapped result).
- review (low): guard the rational denominator in tessellateCurve/evalSurface so
  a degenerate/zero weight set emits a finite point, not NaN (which would poison
  the AABB).
@delta9000 delta9000 merged commit bb99ce1 into main Jun 27, 2026
12 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant