oxideav_obj/decoder.rs
1//! [`Mesh3DDecoder`] adaptors for OBJ + standalone MTL inputs.
2//!
3//! These are thin wrappers around [`crate::obj::parse_obj`] /
4//! [`crate::mtl::parse_mtl_with_scene`] that conform to the
5//! `oxideav-mesh3d` decoder trait. The OBJ decoder carries one
6//! optional knob — free-form curve tessellation (Bezier + B-spline,
7//! rational and non-rational), opt-in via
8//! [`ObjDecoder::with_curve_tessellation`]; the wrapper exists so
9//! registry-based lookup gets a uniform `Box<dyn Mesh3DDecoder>`
10//! factory.
11
12use oxideav_mesh3d::{Mesh3DDecoder, Result, Scene3D};
13
14use crate::{mtl, obj};
15
16/// Wavefront OBJ decoder.
17///
18/// Decodes a self-contained OBJ document into a [`Scene3D`]. Companion
19/// MTL files referenced by `mtllib` directives are not auto-resolved
20/// (the decoder has no file-system context); the material names land
21/// in `Primitive::extras["obj:usemtl"]` and the library file names
22/// land in `Scene3D::extras["obj:mtllibs"]` so the caller can fetch
23/// them and merge via [`crate::mtl::merge_materials_into_scene`].
24///
25/// Use [`obj::parse_obj_with_resolver`] directly when MTL bytes are
26/// already available.
27#[derive(Debug, Default)]
28pub struct ObjDecoder {
29 /// Free-form curve tessellation sample count (default 0 —
30 /// disabled). See [`Self::with_curve_tessellation`].
31 curve_tessellation_samples: u32,
32}
33
34impl ObjDecoder {
35 /// Construct a fresh decoder. Equivalent to `ObjDecoder::default()`.
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 /// Enable tessellation of `cstype` free-form curves *and* `surf`
41 /// surfaces:
42 ///
43 /// * `cstype bezier` / `cstype rat bezier` — de Casteljau
44 /// evaluation on the `[0, 1]` basis domain (1D `curv` curves
45 /// *and* 2D `surf` surfaces via tensor-product de Casteljau).
46 /// * `cstype bspline` / `cstype rat bspline` — Cox-deBoor
47 /// recursive basis on the knot vector supplied by the most-
48 /// recent `parm u …` directive (1D `curv` *and* 2D `surf`
49 /// surfaces via tensor-product Cox-deBoor).
50 /// * `cstype cardinal` / `cstype rat cardinal` — cubic
51 /// Catmull-Rom (1D `curv` *and* 2D `surf` surfaces via
52 /// tensor-product Cardinal evaluation).
53 /// * `cstype taylor` / `cstype rat taylor` — direct
54 /// polynomial evaluation (1D `curv` Horner-rule *and* 2D
55 /// `surf` surfaces via tensor-product bivariate Horner-rule
56 /// evaluation).
57 /// * `cstype bmatrix` — 1D `curv` only.
58 ///
59 /// `samples` is the number of *intervals*. A `curv` curve yields a
60 /// `LineStrip` of `samples + 1` vertices; a Bezier `surf` surface
61 /// yields a `Topology::Triangles` grid of `(samples + 1)²` vertices
62 /// (so `samples == 32` produces a 33×33 lattice, smooth enough for
63 /// diagnostic display without overwhelming the buffer).
64 ///
65 /// Curve primitives land on a synthetic mesh named `"obj:curves"`;
66 /// Bezier surface primitives land on `"obj:surfaces"`. Each carries
67 /// `extras["obj:tessellated_curve"] == true` (surfaces also carry
68 /// `extras["obj:tessellated_surface"] == true`) so downstream
69 /// consumers that don't want the derived geometry can filter it
70 /// out. The free-form directive sequence remains preserved in
71 /// `Scene3D::extras["obj:freeform_directives"]` so re-encoding
72 /// regenerates the original `cstype` / `deg` / `curv` / `surf` /
73 /// `parm` / `end` section.
74 ///
75 /// `samples == 0` (the default) disables tessellation, matching
76 /// the round 1-6 behaviour. Bezier / B-spline / Cardinal / Taylor
77 /// `surf` surfaces are tessellated; basis-matrix surfaces remain
78 /// captured-only.
79 pub fn with_curve_tessellation(mut self, samples: u32) -> Self {
80 self.curve_tessellation_samples = samples;
81 self
82 }
83}
84
85impl Mesh3DDecoder for ObjDecoder {
86 fn decode(&mut self, bytes: &[u8]) -> Result<Scene3D> {
87 let text = std::str::from_utf8(bytes)
88 .map_err(|_| oxideav_mesh3d::Error::invalid("OBJ input contained non-UTF-8 bytes"))?;
89 let options = obj::ParseOptions {
90 curve_tessellation_samples: self.curve_tessellation_samples,
91 };
92 obj::parse_obj_with_options(text, &options, |_path| Ok(Vec::new()))
93 }
94}
95
96/// Standalone MTL decoder. Returns a [`Scene3D`] populated with the
97/// MTL's materials and any external textures they reference; no
98/// meshes / nodes / cameras / lights / animations are added.
99#[derive(Debug, Default)]
100pub struct MtlDecoder {
101 _private: (),
102}
103
104impl MtlDecoder {
105 pub fn new() -> Self {
106 Self::default()
107 }
108}
109
110impl Mesh3DDecoder for MtlDecoder {
111 fn decode(&mut self, bytes: &[u8]) -> Result<Scene3D> {
112 let text = std::str::from_utf8(bytes)
113 .map_err(|_| oxideav_mesh3d::Error::invalid("MTL input contained non-UTF-8 bytes"))?;
114 mtl::parse_mtl_with_scene(text)
115 }
116}