Releases: OxideAV/oxideav-obj
Releases · OxideAV/oxideav-obj
v0.0.4
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), andres 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-handledbzp/bsppatches. 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. Becausecdc/cdpreference vertex
positions by index, theobj:positionsre-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;rescarries only the two segment counts and needs no position
pool. - Round 302: MTL
map_aat onper-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 booleanMaterial::extras["mtl:map_aat"]and round-trips
the exacton/offtoken. The spec documents only theonform,
but the keyword is a boolean state-setter sooffis 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". Withwith_curve_tessellation(samples)enabled, every
constatement now emits a pair of parameter-space
Topology::LineStripseams — one per joined surface edge — on a new
synthetic mesh named"obj:cons". Where round 251 surfaced the eight
raw arguments as the typedScene3D::extras["obj:connectivity"]view,
this pass draws the seam itself ("This information is useful for edge
merging"): each side'scurv2dis resolved through the same
collect_all_curv2_polylinespre-pass thetrim/hole/scrv
passes use, and the[q0, q1]sub-range is walked with the shared
append_curv2_segmentso a connectivity seam is sampled identically to
a special-curve segment. The appendix correspondence (S1(T1(t1))for
t1 ∈ [q0_1, q1_1]joined toS2(T2(t2))fort2 ∈ [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 sharedobj:tessellated_curvesentinel, anobj: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. Aconwithout exactly eight arguments is
dropped from the geometry view; a side whose curve doesn't resolve
(non-positive / undefinedcurv2d, or a zero-length parameter range —
e.g. the spec example's2.0 2.0point-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 themgstate.
Verbatim round-trip is untouched — the encoder filters the seams via the
shared sentinel and replays the originalconline. +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-206scrvpass only emitted the
special curve as a stand-alone parameter-space polyline on the
obj:scrvsmesh; the tessellatedobj:surfacestriangle grid ignored
it, leaving the spec's triangle-edge guarantee unmet. Now everysurf
whose enclosingcstype … endblock carries ascrvdirective has the
special curve embedded into its triangulation. Thescrvis resolved
to a parameter-space polyline (same(u0, u1, curv2d)body grammar and
collect_all_curv2_polylinespre-passtrim/holeuse, 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 provenanceobj:surface_scrv(marker),
obj:surface_scrv_curves(count of special curves that overlapped the
meshed surface), andobj:surface_scrv_vertices(count of synthesised
constraint vertices). Verbatim round-trip is untouched — the synthetic
surface still carries the sharedobj:tessellated_curvesentinel so
the encoder filters it and replays the originalscrvblock 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
provenanceobj:surface_trim_boundary_verticescounts 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_curvesentinel and replays the originaltrim/
holeblock fromScene3D::extras["obj:freeform_directives"]. - Round 273: typed decomposition of the
trim/hole/scrvloop
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 verbatimobj:freeform_directiveschannel
(which still carries everytrim/hole/scrvline 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...
v0.0.3
Other
- Round 188: curv2 2D trimming-curve tessellation
- Round 182: basis-matrix surf surface tessellation
- Round 14: Taylor polynomial
surfsurface 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 everycurv2directive — the parameter-space curve used as
an outer / inner trimming loop, a special curve, or for connectivity
(spec §"curv2") — into aTopology::LineStrippolyline on a new
synthetic mesh named"obj:curves2". Acurv2referencesvp
parameter vertices (spec §"vp u v w") and lies in the 2D parameter
space of the surface it trims, so eachvp (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 3Dcurv
path uses; the sampledx/yare the parameter-space coordinates and
zstays0.0. Unlikecurv, acurv2line carries no inline
u0 u1range — the B-spline evaluation window is taken from the
block'sparm uknot vector ([parm_u[0], parm_u[last]]). The
optional 3rdvpcoordinate is the rational weight (default1.0; a
stored0.0— thevppadding default — is read back as1.0for
the rational forms since a zero weight is degenerate). Negative
curv2indices resolve relative-from-end against thevppool (spec
§"Special point" examplecurv2 -6 -5 …). Synthetic primitives carry
the sharedobj:tessellated_curvesentinel (so the encoder filters
them out and replays the originalcstype/curv2/endblock
verbatim fromScene3D::extras["obj:freeform_directives"]) plus a
obj:curve2 = truemarker and the
obj:curve_kind/obj:curve_degree/obj:curve_u_range/
obj:curve_samplesprovenance. Malformedcurv2blocks (missing
cstype, missing knot vector for B-spline, fewer than two control
points, out-of-rangevpindices) are silently dropped. The
trim/hole/scrvstatements that reference these curves by
index still ride onfreeform_directivesverbatim — surface clipping
against the loops remains future work. - Basis-matrix
surfsurface tessellation under
ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
evaluatessurfelements that sit under acstype bmatrix(or
cstype rat bmatrix) header into a triangulatedTopology::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 frombmat 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 relationparm = (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 + 1polynomial 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 equivalentcstype bezierpatch sample-
for-sample on its single-patch form. Therat bmatrixqualifier
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 (missingbmat u/bmat v,
missingstep, 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 carryobj:tessellated_surface = true,
obj:surface_kind("bmatrix"),obj:surface_degree,
obj:surface_u_range,obj:surface_v_range, and
obj:surface_samplesprovenance plus the shared
obj:tessellated_curve = truesentinel so the encoder filters the
synthetic geometry out and replays the original
cstype/deg/bmat u/bmat v/step/parm u/
parm v/surf/endblock verbatim. Trim/hole loop
evaluation remains out of scope. - Taylor polynomial
surfsurface tessellation under
ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
evaluatessurfelements that sit under acstype taylor(or
cstype rat taylor) header into a triangulatedTopology::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 degreedeg degu degvneeds exactly
(degu + 1) × (degv + 1)coefficient vectors. Thesurf 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", sorat taylorroutes to the same
evaluator without per-vertex weight blending. Synthetic primitives
carryobj:tessellated_surface = true,obj:surface_kind
("taylor"),obj:surface_degree,obj:surface_u_range,
obj:surface_v_range, andobj:surface_samplesprovenance plus the
sharedobj:tessellated_curve = truesentinel so the encoder filters
the synthetic geometry out and replays the original
cstype/deg/surf/parm/endblock verbatim from
Scene3D::extras["obj:freeform_directives"]. Basis-matrixsurf
surfaces remain captured-only.
v0.0.2
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)
surfsurface tessellation under
ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
evaluatessurfelements that sit under acstype cardinal(or
cstype rat cardinal) header into a triangulated
Topology::Trianglesprimitive 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 anydegother than3 3
leaves the surface captured-only. The control grid is read from the
parm u/parm vextents (K = parm_count + 1per direction, from
the spec relationparm = K − n + 2withn = 3); whenparmonly
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 cardinalqualifier 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-vertexwweights are not applied. Synthetic primitives
carryobj:tessellated_surface = true,obj:surface_kind
("cardinal"),obj:surface_degree,obj:surface_u_range,
obj:surface_v_range, andobj:surface_samplesprovenance plus the
sharedobj:tessellated_curve = truesentinel so the encoder filters
the synthetic geometry out and replays the original
cstype/deg/surf/parm u/parm v/endblock verbatim
fromScene3D::extras["obj:freeform_directives"]. Non-cubic Cardinal,
Taylor, and basis-matrixsurfbases remain captured-only. - B-spline / NURBS
surfsurface tessellation under
ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
evaluatessurfelements that sit under acstype bspline(or
cstype rat bspline) header into a triangulatedTopology::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 theparm u/
parm vknot 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. Eachsurfline's
s0 s1 t0 t1range 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-vertexwweights from thev
lines and projects via the weighted denominator
Σ N·N·w·d / Σ N·N·w. The basis is evaluated with the same
bspline_basisCox-deBoor routine the 1Dcurvpath 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, andobj:surface_samples
provenance plus the sharedobj:tessellated_curve = truesentinel so
the encoder filters the synthetic geometry out and replays the original
cstype/deg/surf/parm u/parm v/endblock verbatim
fromScene3D::extras["obj:freeform_directives"]. Knot/control-count
mismatches and malformed blocks are left captured-only. Cardinal /
Taylor / basis-matrixsurfbases remain captured-only. - Bezier
surfsurface tessellation under
ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
evaluatessurfelements that sit under acstype bezier(or
cstype rat bezier) header into a triangulatedTopology::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, …"); thesurfline'sv/vt/vncontrol-
vertex references are parsed for their leading position index, with
negative relative-from-end indices honoured. A single Bezier patch of
declared degreedeg degu degvrequires 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 astepstride) 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 specsurfnote). 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 viax / w. Synthetic
primitives carryobj: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]), andobj:surface_samplesprovenance extras, plus the
sharedobj:tessellated_curve = truesentinel so the encoder filters
the synthetic geometry out and replays the original
cstype/deg/surf/parm/endblock verbatim from
Scene3D::extras["obj:freeform_directives"]. Non-Beziersurfbases
remain captured-only. - Basis-matrix curve tessellation under
ObjDecoder::with_curve_tessellation(samples: u32). The decoder now
evaluatescstype bmatrixcurvdirectives per spec §"Basis matrix"
using the user-supplied(n + 1) × (n + 1)basis frombmat uand the
segment stride fromstep <stepu>(spec §"bmat u/v matrix" and
§"step stepu stepv"). Each polynomial segmenticonsumes the
control-point windowc_{i·step + 1} .. c_{i·step + n + 1}(1-based)
and evaluatesP(t) = Σ_i Σ_j B[i][j] · t^j · p_{base + i}per axis,
whereB[i][j]is the row-major basis-matrix element with column
indexjvarying fastest (spec §"bmat u/v matrix": "matrix lists the
contents of the basis matrix with column subscript j varying the
fastest"). Therat bmatrixqualifier 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 sameobj:tessellated_curve = true/obj:curve_kind("bmatrix") /
obj:curve_degree(fromdeg) /obj:curve_u_range/obj:curve_samples
provenance extras. Malformed blocks (missingbmat u, missingstep,
wrong-size matrix, fewer thann + 1control points) are silently
dropped — the directive sequence still rides on
Scene3D::extras["obj:freeform_directives"]for round-trip. bmatandstepfree-form directive tracking — the parser now
captures these keywords into theobj:freeform_directivesextra
alongside the existingcstype/deg/curv/parm/surf/
trim/hole/scrv/sp/end/bzp/bspdirectives,
so acstype bmatrixblock 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
evaluatescstype cardinalcurvdirectives 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-cubicdegis silently rejected (the directive
itself remains captured for round-trip).
cstype taylorcurvdirectives evaluate via Horner's-rule
polynomial evaluationP(t) = Σ_{i=0..n} c_i · t^iper spec §"Taylor"
(contr...
v0.0.1
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 bper-vertex colour extension (MeshLab / libigl /
Meshroom / OpenCV de-facto). The decoder acceptsvlines with 3, 4,
6, or 7 floats —xyz,xyzw(rational weight per spec
§"v x y z w"),xyzrgb(vertex-colour extension), orxyzwrgb
(both). Colours land onPrimitive::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 (somevlines stay 3-token, others go to 6). The
loader rejects 5-floatvlines as ambiguous (neitherxyzwnor
xyzrgbper any extant convention).- Free-form geometry directives (
vpparameter-space vertices,
cstype,deg,curv,curv2,surf,parm,trim,hole,
scrv,sp,end, plus the older supersededbzp/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 tov/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.
vplines are emitted with only as many coordinates as carry
meaningful information (vp u,vp u v, orvp u v w). p v1 v2 v3 …point elements decode to aTopology::Points
primitive (multi-vertexplines pack onto one element list);
mixing point and face/line elements under oneusemtlsplits into
one primitive per topology.mg <group_number> [res]merging-group state-setting is preserved
verbatim inPrimitive::extras["obj:merging_group"]; a change
mid-stream splits the primitive (mirrorsssmoothing-group
behaviour). The encoder re-emits anmg <token>line ahead of the
affected elements.- Display-attribute state-setters
bevel on/off,c_interp on/off,
d_interp on/off, andlod <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 inMaterial::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.pngrather than dropping the flags. - MTL
d -halo factororientation-dependent dissolve is detected on
parse, surfaced viaMaterial::extras["mtl:d_halo_factor"], and
re-emitted asd -halo <factor>rather than the plaindform. - Encoder rejoins contiguous
Topology::Linessegment pairs into a
single polylinel v1 v2 v3 …line whenever segment N's end index
equals segment N+1's start index, rather than emitting one
l v1 v2per pair (lossless for the typical decode→encode round
trip of polyline-heavy OBJ inputs). - A primitive with exactly one
lelement promotes to the more
specificTopology::LineStrip(orTopology::LineLoopwhen the
last vertex equals the first) instead ofTopology::Lines. The
encoder is symmetric:LineStripemits the natural index list,
LineLoopre-appends the first index so the round-trip parser
re-detects the closure. Multi-lprimitives and 2-vertex
segments stay onTopology::Linesso the existing
contiguous-chain re-emit path still applies. - Multi-name
glines:g name1 name2 …captures every name as a
distinct group entry inPrimitive::extras["obj:groups"]and the
encoder re-emits them on a singlegline. - Smoothing-group state-setting: a mid-object
schange splits the
current primitive so eachPrimitivecarries a single
obj:smoothing_groupvalue;s 0ands offare preserved verbatim
through the round-trip. - MTL
Tf r g b(transmission filter, withg/bdefaulting to
r) andsharpness <value>directives parse into
Material::extrasand re-emit on serialisation. - MTL
Tfalternative-form support —Tf spectral file.rfl factor
lands asMaterial::extras["mtl:Tf:spectral"] = {file, factor}and
Tf xyz x y zlands asMaterial::extras["mtl:Tf:xyz"] = [x, y, z]
(withy/zdefaulting toxper spec). The three forms are
mutually exclusive on emit; the factor1.0default is omitted from
the spectral re-emit so it matches the most common operator-written
spelling. - MTL
disp↔map_disp,decal↔map_decal, andrefl↔
map_reflkeyword aliases land in extras with the original
spelling preserved as the key. - MTL
refl -type sphereandrefl -type cube_*typed reflection-
map sets land as structured extras:mtl:refl:sphere = {file, options?}andmtl:refl:cube = {cube_top, cube_bottom, cube_front, cube_back, cube_left, cube_right, cube_side}(each face an
optional{file, options?}entry). Six separatecube_*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
legacyrefl filenameform still lands inmtl:reflfor
backwards compatibility. obj::parse_obj_from_pathconvenience loader resolvesmtllib
references (single or multi-file per line) against the OBJ's parent
directory; missing libraries surface a cleanError::invalidwith
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/Mesh3DEncodertraits. - OBJ decoder:
v/vt/vnvertex data,ffaces (1-based + negative
indices, all fourv/v/vt/v//vn/v/vt/vnsyntaxes),llines,
oobject split,ggroup,ssmoothing-group capture (extras),
usemtlmaterial switch (onePrimitiveper switch),mtllibmaterial
library load, polygon fan triangulation with original-arity capture in
Mesh::extras["obj:original_face_arities"]. - OBJ encoder: per-mesh
odirective, per-primitiveusemtl, deduplicated
v/vt/vnlists with shared 1-based indices,f-face emission
matching the available attribute set, polygon re-emission when the
matchingobj:original_face_aritiesextra is present,lline elements
forTopology::Lines. - MTL decoder:
Ka/Kd/Ks/KePhong colours,Ns/Ni/d
/Tr/illumscalar parameters,map_Kd/map_Ks/map_Ka/
map_Bump/map_d/map_Nstexture references, Wavefront-PBR
extension (Prroughness,Pmmetallic,Pcclearcoat,Pssheen,
map_Pr/map_PmPBR maps). - MTL encoder:
newmtlblocks with the same vocabulary;d-from-base-color
alpha,Pr/Pmfor PBR-aware rendering pipelines.
v0.0.0
chore: Release package oxideav-obj version 0.0.0