Skip to content

Releases: OxideAV/oxideav-obj

v0.0.4

15 Jun 05:13
9e23ab3

Choose a tag to compare

Other

  • round 308 — capture superseded cdc / cdp / res statements for verbatim round-trip
  • round 302 — MTL map_aat per-material texture anti-aliasing toggle
  • Round 295: draw con connectivity seam as parameter-space polyline pair
  • Round 290: embed scrv special curve as surface triangle edges
  • Round 282: sub-cell trim/hole boundary re-meshing on tessellated surfaces
  • Round 273: typed trim / hole / scrv loop accessor on Scene3D::extras
  • Round 266: typed ctech / stech approximation-technique accessor
  • Round 254: typed parm u/v body-statement accessor on Scene3D::extras
  • Round 251: typed con connectivity accessor on Scene3D::extras
  • drop release-plz.toml — use release-plz defaults across the workspace
  • Round 246: typed sp (special-point) accessor + synthetic-primitive pass
  • Round 243: maplib + usemap rendering-identifier pair
  • Round 240: MTL map_* options typed decomposition
  • Round 236: MTL Ka/Kd/Ks spectral + xyz alternative forms
  • Round 229: connectivity (con) + general-statement (call / csh) round-trip
  • Round 223: ctech / stech / shadow_obj / trace_obj round-trip
  • Round 218: multi-patch Bezier surf surface decomposition
  • Round 212: MTL illum model property decomposition
  • Round 206: scrv special-curve tessellation
  • Round 201: surface trim/hole clipping against curv2 loops

Added

  • Round 308: the three remaining superseded OBJ statements — cdc
    (Cardinal curve), cdp (Cardinal patch), and res useg vseg (the
    segment-count reference/display statement) per spec §"Superseded
    statements" — now round-trip verbatim through
    Scene3D::extras["obj:freeform_directives"], joining the
    already-handled bzp / bsp patches. The spec marks all five
    read-only ("This release is the last release that will read these
    statements. … read in the file and write it out."), so the parser
    captures them on input rather than silently dropping them into the
    lenient-loader fall-through. Because cdc / cdp reference vertex
    positions by index, the obj:positions re-emit condition now also
    fires for those keywords so the referenced position pool survives a
    decode → encode → decode cycle even when no polygonal element consumes
    it; res carries only the two segment counts and needs no position
    pool.
  • Round 302: MTL map_aat on per-material texture anti-aliasing toggle
    per spec §"map_aat on" ("Turns on anti-aliasing of textures in this
    material without anti-aliasing all textures in the scene"). The flag is
    surfaced as a boolean Material::extras["mtl:map_aat"] and round-trips
    the exact on / off token. The spec documents only the on form,
    but the keyword is a boolean state-setter so off is accepted
    symmetrically; any other or missing argument drops the line silently
    (lenient-loader convention) without failing the parse. The serialiser
    emits the flag explicitly (the string-only pass-through loop can't carry
    a bool) ahead of the other pass-through map keywords.
  • Round 295: connectivity (con) seam tessellation — spec §"Connectivity
    between free-form surfaces", §"con surf_1 q0_1 q1_1 curv2d_1 surf_2 q0_2
    q1_2 curv2d_2". With with_curve_tessellation(samples) enabled, every
    con statement now emits a pair of parameter-space
    Topology::LineStrip seams — one per joined surface edge — on a new
    synthetic mesh named "obj:cons". Where round 251 surfaced the eight
    raw arguments as the typed Scene3D::extras["obj:connectivity"] view,
    this pass draws the seam itself ("This information is useful for edge
    merging"): each side's curv2d is resolved through the same
    collect_all_curv2_polylines pre-pass the trim / hole / scrv
    passes use, and the [q0, q1] sub-range is walked with the shared
    append_curv2_segment so a connectivity seam is sampled identically to
    a special-curve segment. The appendix correspondence (S1(T1(t1)) for
    t1 ∈ [q0_1, q1_1] joined to S2(T2(t2)) for t2 ∈ [q0_2, q1_2],
    "identical up to reparameterization", endpoints meeting exactly) means
    the two emitted seams are the two sides of one weld. Per-seam
    provenance: the shared obj:tessellated_curve sentinel, an obj:con
    marker, obj:con_side (1/2), obj:con_surf + obj:con_peer_surf
    (the joined surface's index and its mate's), obj:con_curv2d, and
    obj:con_q0 / obj:con_q1. A con without exactly eight arguments is
    dropped from the geometry view; a side whose curve doesn't resolve
    (non-positive / undefined curv2d, or a zero-length parameter range —
    e.g. the spec example's 2.0 2.0 point-join) drops on its own while
    the other side still emits. The spec's merging-group exclusion
    ("Connectivity between surfaces in different merging groups is ignored")
    is left to the consumer's renderer-side pruning over the mg state.
    Verbatim round-trip is untouched — the encoder filters the seams via the
    shared sentinel and replays the original con line. +9 tests
    (connectivity_seams.rs).
  • Round 290: special-curve (scrv) embedding as surface triangle edges
    (spec §"Special curve": "A special curve is guaranteed to be included
    in any triangulation of the surface. … the line formed by
    approximating the special curve with a sequence of straight line
    segments will actually appear as a sequence of triangle edges in the
    final triangulation"). The round-206 scrv pass only emitted the
    special curve as a stand-alone parameter-space polyline on the
    obj:scrvs mesh; the tessellated obj:surfaces triangle grid ignored
    it, leaving the spec's triangle-edge guarantee unmet. Now every surf
    whose enclosing cstype … end block carries a scrv directive has the
    special curve embedded into its triangulation. The scrv is resolved
    to a parameter-space polyline (same (u0, u1, curv2d) body grammar and
    collect_all_curv2_polylines pre-pass trim / hole use, but left
    open — a special curve is a constraint, not a closed region), then each
    straight segment is forced to coincide with a chain of triangle edges.
    The constraint runs on the final kept geometry after the round-282
    trim/hole re-mesh, so trimming and special-curve embedding compose. The
    embedder operates on the triangle soup with no adjacency structure: any
    triangle whose interior a segment crosses is split so the chord between
    the two boundary hits becomes an edge; crossing vertices are
    deduplicated on a quantised parameter grid (watertight across adjacent
    splits); each synthesised vertex's 3D position is the barycentric blend
    of its host triangle's corners (so the embedded curve is exactly as
    accurate as the surrounding piecewise-linear facet — no new surface
    evaluation is introduced); and a segment already coincident with
    existing lattice edges counts as embedded with no split. New
    per-surface provenance obj:surface_scrv (marker),
    obj:surface_scrv_curves (count of special curves that overlapped the
    meshed surface), and obj:surface_scrv_vertices (count of synthesised
    constraint vertices). Verbatim round-trip is untouched — the synthetic
    surface still carries the shared obj:tessellated_curve sentinel so
    the encoder filters it and replays the original scrv block from
    Scene3D::extras["obj:freeform_directives"].
  • Round 282: sub-cell trim/hole boundary re-meshing (spec §"Trimming
    loops and holes", §"trim u0 u1 curv2d …", §"hole u0 u1 curv2d …").
    The round-201 conservative clip dropped any lattice triangle whose
    three corners didn't all classify inside the trimmed region, so the
    trim edge stayed jagged at the lattice grain. Straddling boundary
    triangles (1 or 2 corners kept) are now clipped against the in/out
    classification function instead of dropped wholesale: each crossing
    lattice edge is bisected in parameter space until the inside/outside
    frontier is pinned (24 rounds ≈ 2⁻²⁴ of the edge length), the
    synthesised boundary vertex — 3D position interpolated linearly
    along the lattice edge, the same piecewise-linear approximation the
    triangle lattice itself carries — is appended after the
    (samples + 1)² lattice block, and the kept sub-polygon (corner
    triangle for 1-kept, quad split into two triangles for 2-kept) is
    emitted with the original CCW winding. Crossings are cached per
    undirected lattice edge so adjacent straddling cells share their
    boundary vertex and the re-meshed rim stays watertight;
    sub-triangles whose parameter-space area collapses below 10⁻⁶ of a
    lattice cell (loops grazing a lattice line exactly) are suppressed
    rather than emitted as degenerate slivers, and boundary vertices
    left unreferenced by that suppression are garbage-collected from
    the vertex pool. On an axis-aligned square loop sitting between
    lattice lines the kept area now matches the analytic trimmed area
    to within the chord-across-corner error (~0.4 % observed at
    8 samples) where the conservative whole-cell staircase missed
    ≈ 60 % of the loop on the same fixture. New per-primitive
    provenance obj:surface_trim_boundary_vertices counts the
    synthesised vertices (0 when every straddling cell collapsed to
    suppressed slivers). Verbatim round-trip is untouched — the encoder
    still filters synthetic surfaces via the shared
    obj:tessellated_curve sentinel and replays the original trim /
    hole block from Scene3D::extras["obj:freeform_directives"].
  • Round 273: typed decomposition of the trim / hole / scrv loop
    body statements per spec §"Trimming loops and holes" / §"trim u0 u1
    curv2d …" / §"hole u0 u1 curv2d …" and §"Special curve" / §"scrv u0
    u1 curv2d …". All three share the identical repeating-triple body
    shape (the keyword followed by one or more (u0, u1, curv2d)
    triples). Parallel to the verbatim obj:freeform_directives channel
    (which still carries every trim / hole / scrv line for
    round-trip), a parse-time-only typed view now lands on
    Scene3D::extras["obj:trim_loops"] as an array of objects with the
    four stable, low...
Read more

v0.0.3

30 May 02:37
262971c

Choose a tag to compare

Other

  • Round 188: curv2 2D trimming-curve tessellation
  • Round 182: basis-matrix surf surface tessellation
  • Round 14: Taylor polynomial surf surface tessellation
  • Round 14 (depth): cargo-fuzz harness + two parse-time panic fixes

Added

  • 2D trimming-curve (curv2) tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates every curv2 directive — the parameter-space curve used as
    an outer / inner trimming loop, a special curve, or for connectivity
    (spec §"curv2") — into a Topology::LineStrip polyline on a new
    synthetic mesh named "obj:curves2". A curv2 references vp
    parameter vertices (spec §"vp u v w") and lies in the 2D parameter
    space of the surface it trims, so each vp (u, v) is lifted into a
    [u, v, 0.0] control point and run through the same Bezier /
    B-spline / Cardinal / Taylor / basis-matrix evaluators the 3D curv
    path uses; the sampled x/y are the parameter-space coordinates and
    z stays 0.0. Unlike curv, a curv2 line carries no inline
    u0 u1 range — the B-spline evaluation window is taken from the
    block's parm u knot vector ([parm_u[0], parm_u[last]]). The
    optional 3rd vp coordinate is the rational weight (default 1.0; a
    stored 0.0 — the vp padding default — is read back as 1.0 for
    the rational forms since a zero weight is degenerate). Negative
    curv2 indices resolve relative-from-end against the vp pool (spec
    §"Special point" example curv2 -6 -5 …). Synthetic primitives carry
    the shared obj:tessellated_curve sentinel (so the encoder filters
    them out and replays the original cstype / curv2 / end block
    verbatim from Scene3D::extras["obj:freeform_directives"]) plus a
    obj:curve2 = true marker and the
    obj:curve_kind / obj:curve_degree / obj:curve_u_range /
    obj:curve_samples provenance. Malformed curv2 blocks (missing
    cstype, missing knot vector for B-spline, fewer than two control
    points, out-of-range vp indices) are silently dropped. The
    trim / hole / scrv statements that reference these curves by
    index still ride on freeform_directives verbatim — surface clipping
    against the loops remains future work.
  • Basis-matrix surf surface tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates surf elements that sit under a cstype bmatrix (or
    cstype rat bmatrix) header into a triangulated Topology::Triangles
    primitive on the synthetic "obj:surfaces" mesh, via the bivariate
    tensor-product polynomial
    S(u, v) = Σ_a Σ_b (Σ_p B_u[a][p] · u^p) (Σ_q B_v[b][q] · v^q) · c_{base_u + a, base_v + b} (spec §"Basis matrix",
    §"bmat u/v matrix", §"step stepu stepv"). Per-direction basis
    matrices come from bmat u / bmat v (row-major, column index
    varying fastest); per-direction segment strides come from
    step stepu stepv. The per-direction control-grid extent is the
    inverse of the spec relation parm = (K − n) / s + 2, i.e.
    K = (parm − 2) · s + n + 1, applied independently in u and v per
    spec §"step stepu stepv" ("For surfaces, the above description
    applies independently to each parametric direction."). Multi-patch
    grids are now decomposed into (K_u − n − 1) / stepu + 1 × (K_v − m − 1) / stepv + 1 polynomial segments; the global parameter (u, v) partitions into per-segment (seg_u, seg_v, t_u, t_v) with the
    patch-local control window starting at (seg_u · stepu, seg_v · stepv). The cubic Bezier basis-matrix surface from the spec
    §"Examples" reproduces the equivalent cstype bezier patch sample-
    for-sample on its single-patch form. The rat bmatrix qualifier
    routes to the same evaluator without per-vertex weight blending
    (matches the round-10 1D curve behaviour — the user's basis is the
    authoritative source). Malformed blocks (missing bmat u / bmat v,
    missing step, wrong-size matrices, mismatched control-vertex
    count) are silently dropped — the directive sequence still rides on
    Scene3D::extras["obj:freeform_directives"] for round-trip.
    Synthetic primitives carry obj:tessellated_surface = true,
    obj:surface_kind ("bmatrix"), 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 / bmat u / bmat v / step / parm u /
    parm v / surf / end block verbatim. Trim/hole loop
    evaluation remains out of scope.
  • Taylor polynomial surf surface tessellation under
    ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
    evaluates surf elements that sit under a cstype taylor (or
    cstype rat taylor) header into a triangulated Topology::Triangles
    primitive on the synthetic "obj:surfaces" mesh, via the bivariate
    tensor-product Horner-rule evaluation
    S(u, v) = Σ_i Σ_j c_{i,j} · u^i · v^j (spec §"Taylor"). Control
    points are the polynomial coefficients in spec §"Surface vertex data
    — control points" row-major u-fastest order; a single Taylor patch
    of declared degree deg degu degv needs exactly
    (degu + 1) × (degv + 1) coefficient vectors. The surf s0 s1 t0 t1
    range supplies the global parameter clip; Taylor surfaces evaluate
    against the raw [s0, s1] × [t0, t1] window directly (not a
    normalised [0, 1] re-parameterisation). The implementation
    collapses the inner u sum via Horner's rule across each v-row, then
    a second Horner-rule pass in v over the collapsed points; total
    surface sample count is (samples + 1)². The spec note in
    §"Free-form curve/surface body statements" says the rational form
    "does not make sense for Taylor", so rat taylor routes to the same
    evaluator without per-vertex weight blending. Synthetic primitives
    carry obj:tessellated_surface = true, obj:surface_kind
    ("taylor"), 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 / end block verbatim from
    Scene3D::extras["obj:freeform_directives"]. Basis-matrix surf
    surfaces remain captured-only.

v0.0.2

24 May 12:09
10a1773

Choose a tag to compare

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"
    (contr...
Read more

v0.0.1

10 May 20:46
1806af9

Choose a tag to compare

Other

  • Round 6: per-vertex colour extension + v 4th weight preservation
  • round-5 status — Tf alt forms, typed refl, line strip/loop
  • Round 5: refl -type sphere / cube_* typed reflection-map sets
  • Round 5: promote single-l polylines to LineStrip / LineLoop topology
  • Round 5: MTL Tf spectral / Tf xyz alternative forms
  • Round 4: free-form geometry directives round-trip via Scene3D::extras
  • Round 3: encoder rejoins polyline segment chains into one l line
  • Round 3: MTL map_* option flags + d -halo dissolve
  • Round 3: bevel / c_interp / d_interp / lod display attributes
  • Round 3: p point elements + mg merging-group state-setting
  • Round 2: multi-name groups, smoothing-group split, MTL extras, path loader, negative-index encoder

Added

  • v x y z r g b per-vertex colour extension (MeshLab / libigl /
    Meshroom / OpenCV de-facto). The decoder accepts v lines with 3, 4,
    6, or 7 floats — xyz, xyzw (rational weight per spec
    §"v x y z w"), xyzrgb (vertex-colour extension), or xyzwrgb
    (both). Colours land on Primitive::colors[0] as
    [r, g, b, 1.0]; rational weights land in
    Primitive::extras["obj:vertex_weight"]. A per-vertex bitmap in
    Primitive::extras["obj:vertex_color_present"] records which
    source vertices originally carried RGB so the encoder can re-emit
    the same 3-/4-/6-/7-token width on round-trip rather than
    fabricating synthetic white for vertices that didn't spell out
    colour. Mixed-colouring primitives round-trip with the partition
    preserved (some v lines stay 3-token, others go to 6). The
    loader rejects 5-float v lines as ambiguous (neither xyzw nor
    xyzrgb per any extant convention).
  • Free-form geometry directives (vp parameter-space vertices,
    cstype, deg, curv, curv2, surf, parm, trim, hole,
    scrv, sp, end, plus the older superseded bzp / bsp
    patches per spec §"Superseded statements") are captured into
    Scene3D::extras["obj:vp"] (list of [u, v, w] 3-tuples in 1-based
    numbering parallel to v / vt / vn) and
    Scene3D::extras["obj:freeform_directives"] (sequence of
    [keyword, arg1, arg2, …] arrays preserving directive order and
    arguments verbatim). The encoder replays both after the polygonal
    section so a decode → encode round-trip is bit-stable for the
    free-form portion. No semantic interpretation — consumers that
    need to evaluate the curves/surfaces walk the captured directive
    sequence themselves; this crate guarantees lossless transit.
    vp lines are emitted with only as many coordinates as carry
    meaningful information (vp u, vp u v, or vp u v w).
  • p v1 v2 v3 … point elements decode to a Topology::Points
    primitive (multi-vertex p lines pack onto one element list);
    mixing point and face/line elements under one usemtl splits into
    one primitive per topology.
  • mg <group_number> [res] merging-group state-setting is preserved
    verbatim in Primitive::extras["obj:merging_group"]; a change
    mid-stream splits the primitive (mirrors s smoothing-group
    behaviour). The encoder re-emits an mg <token> line ahead of the
    affected elements.
  • Display-attribute state-setters bevel on/off, c_interp on/off,
    d_interp on/off, and lod <level> are captured per-primitive in
    Primitive::extras["obj:bevel"] / ["obj:c_interp"] /
    ["obj:d_interp"] / ["obj:lod"]. Mid-stream changes split the
    primitive so each one carries one consistent assignment per
    attribute.
  • MTL map_* directive option flags (-blendu, -blendv, -cc,
    -clamp, -bm, -boost, -mm, -o, -s, -t, -texres,
    -imfchan, -type) are stripped out of the filename at parse
    time and preserved in Material::extras["mtl:<map_name>:options"]
    as an array of "<flag> <args>" strings. The encoder splices the
    saved options back ahead of the filename so a round-trip emits
    map_Kd -clamp on path.png rather than dropping the flags.
  • MTL d -halo factor orientation-dependent dissolve is detected on
    parse, surfaced via Material::extras["mtl:d_halo_factor"], and
    re-emitted as d -halo <factor> rather than the plain d form.
  • Encoder rejoins contiguous Topology::Lines segment pairs into a
    single polyline l v1 v2 v3 … line whenever segment N's end index
    equals segment N+1's start index, rather than emitting one
    l v1 v2 per pair (lossless for the typical decode→encode round
    trip of polyline-heavy OBJ inputs).
  • A primitive with exactly one l element promotes to the more
    specific Topology::LineStrip (or Topology::LineLoop when the
    last vertex equals the first) instead of Topology::Lines. The
    encoder is symmetric: LineStrip emits the natural index list,
    LineLoop re-appends the first index so the round-trip parser
    re-detects the closure. Multi-l primitives and 2-vertex
    segments stay on Topology::Lines so the existing
    contiguous-chain re-emit path still applies.
  • Multi-name g lines: g name1 name2 … captures every name as a
    distinct group entry in Primitive::extras["obj:groups"] and the
    encoder re-emits them on a single g line.
  • Smoothing-group state-setting: a mid-object s change splits the
    current primitive so each Primitive carries a single
    obj:smoothing_group value; s 0 and s off are preserved verbatim
    through the round-trip.
  • MTL Tf r g b (transmission filter, with g / b defaulting to
    r) and sharpness <value> directives parse into
    Material::extras and re-emit on serialisation.
  • MTL Tf alternative-form support — Tf spectral file.rfl factor
    lands as Material::extras["mtl:Tf:spectral"] = {file, factor} and
    Tf xyz x y z lands as Material::extras["mtl:Tf:xyz"] = [x, y, z]
    (with y / z defaulting to x per spec). The three forms are
    mutually exclusive on emit; the factor 1.0 default is omitted from
    the spectral re-emit so it matches the most common operator-written
    spelling.
  • MTL dispmap_disp, decalmap_decal, and refl
    map_refl keyword aliases land in extras with the original
    spelling preserved as the key.
  • MTL refl -type sphere and refl -type cube_* typed reflection-
    map sets land as structured extras: mtl:refl:sphere = {file, options?} and mtl:refl:cube = {cube_top, cube_bottom, cube_front, cube_back, cube_left, cube_right, cube_side} (each face an
    optional {file, options?} entry). Six separate cube_* lines
    bundle into one cubemap rather than overwriting each other under
    the legacy single-string slot. The encoder re-emits one line per
    face / sphere with options spliced ahead of the filename, in a
    fixed face order so the round-trip diff is deterministic. The bare
    legacy refl filename form still lands in mtl:refl for
    backwards compatibility.
  • obj::parse_obj_from_path convenience loader resolves mtllib
    references (single or multi-file per line) against the OBJ's parent
    directory; missing libraries surface a clean Error::invalid with
    the offending path.
  • ObjEncoder::with_negative_indices(true) (and the underlying
    obj::SerializeOptions::negative_indices) emit face / line vertex
    indices in relative-from-end form (f -3 -2 -1) for round-trip
    parity with inputs that used negative indices.

Initial scaffold

  • Wavefront OBJ + companion MTL parser/serialiser implementing
    oxideav_mesh3d::Mesh3DDecoder / Mesh3DEncoder traits.
  • OBJ decoder: v / vt / vn vertex data, f faces (1-based + negative
    indices, all four v / v/vt / v//vn / v/vt/vn syntaxes), l lines,
    o object split, g group, s smoothing-group capture (extras),
    usemtl material switch (one Primitive per switch), mtllib material
    library load, polygon fan triangulation with original-arity capture in
    Mesh::extras["obj:original_face_arities"].
  • OBJ encoder: per-mesh o directive, per-primitive usemtl, deduplicated
    v / vt / vn lists with shared 1-based indices, f-face emission
    matching the available attribute set, polygon re-emission when the
    matching obj:original_face_arities extra is present, l line elements
    for Topology::Lines.
  • MTL decoder: Ka / Kd / Ks / Ke Phong colours, Ns / Ni / d
    / Tr / illum scalar parameters, map_Kd / map_Ks / map_Ka /
    map_Bump / map_d / map_Ns texture references, Wavefront-PBR
    extension (Pr roughness, Pm metallic, Pc clearcoat, Ps sheen,
    map_Pr / map_Pm PBR maps).
  • MTL encoder: newmtl blocks with the same vocabulary; d-from-base-color
    alpha, Pr / Pm for PBR-aware rendering pipelines.

v0.0.0

10 May 02:15

Choose a tag to compare

chore: Release package oxideav-obj version 0.0.0