Skip to main content

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}