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 bmatrix` — 1D `curv` only.
54    ///
55    /// `samples` is the number of *intervals*. A `curv` curve yields a
56    /// `LineStrip` of `samples + 1` vertices; a Bezier `surf` surface
57    /// yields a `Topology::Triangles` grid of `(samples + 1)²` vertices
58    /// (so `samples == 32` produces a 33×33 lattice, smooth enough for
59    /// diagnostic display without overwhelming the buffer).
60    ///
61    /// Curve primitives land on a synthetic mesh named `"obj:curves"`;
62    /// Bezier surface primitives land on `"obj:surfaces"`. Each carries
63    /// `extras["obj:tessellated_curve"] == true` (surfaces also carry
64    /// `extras["obj:tessellated_surface"] == true`) so downstream
65    /// consumers that don't want the derived geometry can filter it
66    /// out. The free-form directive sequence remains preserved in
67    /// `Scene3D::extras["obj:freeform_directives"]` so re-encoding
68    /// regenerates the original `cstype` / `deg` / `curv` / `surf` /
69    /// `parm` / `end` section.
70    ///
71    /// `samples == 0` (the default) disables tessellation, matching
72    /// the round 1-6 behaviour. Bezier / B-spline / Cardinal `surf`
73    /// surfaces are tessellated; Taylor / basis-matrix surfaces remain
74    /// captured-only.
75    pub fn with_curve_tessellation(mut self, samples: u32) -> Self {
76        self.curve_tessellation_samples = samples;
77        self
78    }
79}
80
81impl Mesh3DDecoder for ObjDecoder {
82    fn decode(&mut self, bytes: &[u8]) -> Result<Scene3D> {
83        let text = std::str::from_utf8(bytes)
84            .map_err(|_| oxideav_mesh3d::Error::invalid("OBJ input contained non-UTF-8 bytes"))?;
85        let options = obj::ParseOptions {
86            curve_tessellation_samples: self.curve_tessellation_samples,
87        };
88        obj::parse_obj_with_options(text, &options, |_path| Ok(Vec::new()))
89    }
90}
91
92/// Standalone MTL decoder. Returns a [`Scene3D`] populated with the
93/// MTL's materials and any external textures they reference; no
94/// meshes / nodes / cameras / lights / animations are added.
95#[derive(Debug, Default)]
96pub struct MtlDecoder {
97    _private: (),
98}
99
100impl MtlDecoder {
101    pub fn new() -> Self {
102        Self::default()
103    }
104}
105
106impl Mesh3DDecoder for MtlDecoder {
107    fn decode(&mut self, bytes: &[u8]) -> Result<Scene3D> {
108        let text = std::str::from_utf8(bytes)
109            .map_err(|_| oxideav_mesh3d::Error::invalid("MTL input contained non-UTF-8 bytes"))?;
110        mtl::parse_mtl_with_scene(text)
111    }
112}