Skip to content

v0.0.2

Choose a tag to compare

@MagicalTux MagicalTux released this 24 May 12:09
· 35 commits to master since this release
10a1773

Other

  • Round 13: Cardinal (Catmull-Rom) surf surface tessellation
  • Round 12: B-spline / NURBS surf surface tessellation
  • round 11: Bezier surf surface tessellation (tensor-product de Casteljau)
  • round 10: basis-matrix curve tessellation (cstype bmatrix + bmat + step)
  • Round 9: Cardinal (Catmull-Rom) + Taylor curve tessellation
  • Round 8: B-spline / NURBS curve tessellation (#3)
  • Round 7: Bezier curve tessellation evaluator

Added

  • Cardinal (Catmull-Rom) surf surface tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates surf elements that sit under a cstype cardinal (or
    cstype rat cardinal) header into a triangulated
    Topology::Triangles primitive on the synthetic "obj:surfaces"
    mesh, via the bivariate tensor-product Cardinal evaluation
    S(u, v) = Σ_i Σ_j C_i(u) · C_j(v) · d_{i,j} (spec §"Cardinal"). Each
    parametric direction reuses the spec's Cardinal→Bezier per-segment
    conversion (b0 = c1, b1 = c1 + (c2 − c0) / 6,
    b2 = c2 − (c3 − c1) / 6, b3 = c2, then a cubic Bernstein blend)
    over a sliding 4-point window: the inner pass collapses every v-row at
    the sample u, then a second 1-D Cardinal pass runs in v over the
    collapsed points. Cardinal is cubic-only per spec ("Cardinal splines
    are only defined for the cubic case"), so any deg other than 3 3
    leaves the surface captured-only. The control grid is read from the
    parm u / parm v extents (K = parm_count + 1 per direction, from
    the spec relation parm = K − n + 2 with n = 3); when parm only
    carries the 2-value global parameter range (as the spec's
    Cardinal-surface example does), the grid is taken to be the square
    single patch (cols = rows = √total). Per spec §"Cardinal" — "For
    surfaces, all but the first and last row and column of control points
    are interpolated" — a single bicubic patch's parametric corners land
    exactly on the interior 2×2 control block, which the tests verify, and
    a cross-check confirms the tensor-product evaluator matches an
    independent Cardinal→Bezier reference sample-for-sample. The
    rat cardinal qualifier routes to the same evaluator (spec
    §"Free-form curve/surface body statements" notes the unit-weight
    default is reasonable for Cardinal because its basis functions sum to
    1), so per-vertex w weights are not applied. Synthetic primitives
    carry obj:tessellated_surface = true, obj:surface_kind
    ("cardinal"), obj:surface_degree, obj:surface_u_range,
    obj:surface_v_range, and obj:surface_samples provenance plus the
    shared obj:tessellated_curve = true sentinel so the encoder filters
    the synthetic geometry out and replays the original
    cstype / deg / surf / parm u / parm v / end block verbatim
    from Scene3D::extras["obj:freeform_directives"]. Non-cubic Cardinal,
    Taylor, and basis-matrix surf bases remain captured-only.
  • B-spline / NURBS surf surface tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates surf elements that sit under a cstype bspline (or
    cstype rat bspline) header into a triangulated Topology::Triangles
    primitive on the synthetic "obj:surfaces" mesh, via the bivariate
    tensor-product Cox-deBoor formula
    S(u, v) = Σ_i Σ_j N_{i,nu}(u) · N_{j,nv}(v) · d_{i,j} (spec
    §"B-spline" + §"Rational and non-rational curves and surfaces"). The
    per-direction control-grid extents are derived from the parm u /
    parm v knot vectors ((len(parm u) − degu − 1) × (len(parm v) − degv − 1) per spec §"B-spline" condition 6, applied
    independently in u and v); control points are read in the spec's
    row-major u-fastest order (§"Surface vertex data — control points")
    with negative relative-from-end indices honoured. Each surf line's
    s0 s1 t0 t1 range is clipped against the condition-5 evaluation
    window [x_n, x_{K+1}] of its direction's knot vector, and the last
    sample per direction is nudged fractionally below the upper bound so
    the half-open knot-span convention doesn't zero the basis at the
    endpoint (same NURBS-evaluator pattern as the round-8 curve path). The
    rational (NURBS) form blends the per-vertex w weights from the v
    lines and projects via the weighted denominator
    Σ N·N·w·d / Σ N·N·w. The basis is evaluated with the same
    bspline_basis Cox-deBoor routine the 1D curv path uses, so a
    clamped quadratic B-spline patch (parm 0 0 0 1 1 1) reproduces the
    equivalent quadratic Bezier patch sample-for-sample, and the spec's
    cubic B-spline surface example tessellates inside its control-net
    convex hull. Synthetic primitives carry
    obj:tessellated_surface = true, obj:surface_kind
    ("bspline" / "rat_bspline"), obj:surface_degree,
    obj:surface_u_range, obj:surface_v_range, and obj:surface_samples
    provenance plus the shared obj:tessellated_curve = true sentinel so
    the encoder filters the synthetic geometry out and replays the original
    cstype / deg / surf / parm u / parm v / end block verbatim
    from Scene3D::extras["obj:freeform_directives"]. Knot/control-count
    mismatches and malformed blocks are left captured-only. Cardinal /
    Taylor / basis-matrix surf bases remain captured-only.
  • Bezier surf surface tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates surf elements that sit under a cstype bezier (or
    cstype rat bezier) header into a triangulated Topology::Triangles
    primitive on a synthetic mesh named "obj:surfaces", via the
    bivariate tensor-product de Casteljau algorithm (spec §"Rational and
    non-rational curves and surfaces" + §"Bezier"). Control points are
    read in the spec's row-major u-fastest order (§"Surface vertex data —
    control points": "listed in the order i = 0 to K1 for j = 0, followed
    by i = 0 to K1 for j = 1, …"); the surf line's v/vt/vn control-
    vertex references are parsed for their leading position index, with
    negative relative-from-end indices honoured. A single Bezier patch of
    declared degree deg degu degv requires exactly
    (degu + 1) × (degv + 1) control points; counts that don't match a
    single patch (multi-patch grids, which the Bezier basis can't
    decompose without a step stride) are left captured-only. The patch
    is sampled at a (samples + 1) × (samples + 1) lattice and
    triangulated counter-clockwise (front = u-increases-right,
    v-increases-up per the spec surf note). The rational form lifts each
    control point to its homogeneous (w·x, w·y, w·z, w) form, runs both
    de Casteljau passes in 4D, and projects back via x / w. Synthetic
    primitives carry obj:tessellated_surface = true, obj:surface_kind
    ("bezier" / "rat_bezier"), obj:surface_degree ([degu, degv]),
    obj:surface_u_range ([s0, s1]), obj:surface_v_range
    ([t0, t1]), and obj:surface_samples provenance extras, plus the
    shared obj:tessellated_curve = true sentinel so the encoder filters
    the synthetic geometry out and replays the original
    cstype / deg / surf / parm / end block verbatim from
    Scene3D::extras["obj:freeform_directives"]. Non-Bezier surf bases
    remain captured-only.
  • Basis-matrix curve tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates cstype bmatrix curv directives per spec §"Basis matrix"
    using the user-supplied (n + 1) × (n + 1) basis from bmat u and the
    segment stride from step <stepu> (spec §"bmat u/v matrix" and
    §"step stepu stepv"). Each polynomial segment i consumes the
    control-point window c_{i·step + 1} .. c_{i·step + n + 1} (1-based)
    and evaluates P(t) = Σ_i Σ_j B[i][j] · t^j · p_{base + i} per axis,
    where B[i][j] is the row-major basis-matrix element with column
    index j varying fastest (spec §"bmat u/v matrix": "matrix lists the
    contents of the basis matrix with column subscript j varying the
    fastest"). The rat bmatrix qualifier is accepted but does not apply
    per-vertex weights (the spec note says the unit-weight default "may
    or may not make sense for a representation given in basis-matrix form",
    so the user's basis is the authoritative source). Synthetic primitives
    carry the same obj:tessellated_curve = true / obj:curve_kind ("bmatrix") /
    obj:curve_degree (from deg) / obj:curve_u_range / obj:curve_samples
    provenance extras. Malformed blocks (missing bmat u, missing step,
    wrong-size matrix, fewer than n + 1 control points) are silently
    dropped — the directive sequence still rides on
    Scene3D::extras["obj:freeform_directives"] for round-trip.
  • bmat and step free-form directive tracking — the parser now
    captures these keywords into the obj:freeform_directives extra
    alongside the existing cstype / deg / curv / parm / surf /
    trim / hole / scrv / sp / end / bzp / bsp directives,
    so a cstype bmatrix block round-trips through a decode → encode →
    decode cycle bit-exactly. The encoder replays the captured directive
    sequence verbatim with no semantic interpretation.
  • Cardinal (Catmull-Rom) + Taylor polynomial curve tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates cstype cardinal curv directives via the spec §"Cardinal"
    conversion to Bezier control points
    (b0 = c1, b1 = c1 + (c2 − c0) / 6, b2 = c2 − (c3 − c1) / 6,
    b3 = c2, then cubic Bezier blend) on a sliding 4-point window across
    the control polygon, producing C¹-continuous polylines that
    interpolate every interior control point exactly. Cardinal is cubic
    only per spec; non-cubic deg is silently rejected (the directive
    itself remains captured for round-trip).
    cstype taylor curv directives evaluate via Horner's-rule
    polynomial evaluation P(t) = Σ_{i=0..n} c_i · t^i per spec §"Taylor"
    (control points are the polynomial coefficients) with sampling across
    the [u_min, u_max] range supplied on the curv line. Both rat cardinal and rat taylor qualifiers are accepted but route to the
    same evaluator (the spec note says the unit-weight default is
    reasonable for Cardinal because its basis functions sum to 1, and
    explicitly that the rational form "does not make sense for Taylor").
    The resulting polylines land on the existing "obj:curves" synthetic
    mesh with obj:tessellated_curve = true / obj:curve_kind
    ("cardinal" / "taylor") / obj:curve_degree (3 for Cardinal,
    the deg value for Taylor) / obj:curve_u_range / obj:curve_samples
    provenance extras. The encoder filters synthetic primitives out of
    the polygonal section so a re-encode replays the original
    cstype cardinal / cstype taylor blocks unchanged.
  • B-spline / NURBS curve tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates cstype bspline and cstype rat bspline curv directives
    via the Cox-deBoor recursive basis-function formula (spec §"B-spline"),
    clipped against the [x_n, x_{K+1}] evaluation window of the knot
    vector supplied by the most-recent parm u … body statement. The
    resulting polyline lands on the existing "obj:curves" synthetic
    mesh with the same obj:tessellated_curve = true /
    obj:curve_kind ("bspline" / "rat_bspline") /
    obj:curve_degree / obj:curve_u_range / obj:curve_samples
    provenance extras. Rational form (NURBS) uses the per-vertex 4th w
    weight and projects the weighted blend back to 3D, matching the
    spec's Σ N_{i,n} · w_i · d_i / Σ N_{i,n} · w_i formulation.
    Knot-vector length is validated against the spec condition
    len == K + degree + 2 and incomplete curves are skipped silently
    (the directive itself remains captured for round-trip). The
    tessellator now does two-pass per-block traversal so the curv
    header (which precedes the parm u body statement per spec
    §"Specifying free-form curves/surfaces") still resolves its knot
    vector. Cardinal / Taylor / basis-matrix bases and surf
    2-parameter surfaces remain captured-only.
  • ObjDecoder::with_curve_tessellation(samples: u32) evaluates every
    cstype bezier (and cstype rat bezier) curv directive via de
    Casteljau's algorithm at samples + 1 uniformly-spaced parameter
    values, producing real Topology::LineStrip primitives on a synthetic
    mesh named "obj:curves". Rational form uses the per-vertex 4th w
    weight (v x y z w) and projects the homogeneous blend back to 3D.
    Each tessellated primitive carries obj:tessellated_curve = true,
    obj:curve_kind ("bezier" / "rat_bezier"), obj:curve_degree,
    obj:curve_u_range, and obj:curve_samples in extras so consumers
    can filter / inspect derived geometry. samples == 0 (the default)
    preserves the round 1-6 behaviour: directives ride as
    Scene3D::extras["obj:freeform_directives"] only, no synthetic mesh.
    The encoder skips synthetic curve primitives so re-encoding produces
    the original cstype / curv / end section unchanged.
  • Source position pool round-trip: when an OBJ's free-form section
    (curv / curv2 / surf / bzp / bsp) references positions by
    absolute index, those positions now ride on
    Scene3D::extras["obj:positions"] (and parallel
    obj:position_weights / obj:position_colors arrays for the
    extension widths) so the encoder re-emits the full v block in
    source order. Previously, positions only referenced by free-form
    directives were silently dropped on re-encode, which made absolute
    curve-control-point indices drift after a decode → encode → decode
    cycle. The fix is invisible to polygon-only OBJs; the extras are
    populated only when free-form directives that reference indices are
    present.