Files
effectcraft/crates/model/src/obj.rs
T
Dylan M. Taylor 63fda85a51 3D models: bound and share decoded textures, share skin matrices, index OBJ materials
* fix(model): share decoded textures and skin matrices, bound texture pixels, index OBJ materials

Texture::decode expanded any image to float RGBA without a size check. Repeated mtllib lines
reparsed the library and decoded its textures again; glTF textures with a non-default sampler
each copied their image's pixels; every instance of a skin built its own joint matrices. OBJ
usemtl and faces scanned the growing material and primitive lists, and contour grouping kept
every ancestor of every contour.

* address review: keep largest contours and dedupe mtllib by set

* address review: contour depth counts containers, glTF wrap copies and FBX textures share the import pixel budget

* address review: holes go to the innermost outline; glTF wrap variants keyed by decoded image

* address review: create every outline slot before attaching holes

* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)

* Models: left-out textures are import warnings; the import texture budget is 2^29 pixels

* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)

* Import: a model's left-out parts reach the user as an import warning

* FBX textures move to #2955 (it reworks the same code)
2026-11-12 07:00:15 -05:00

38 lines
1.8 KiB
Rust

//! Wavefront OBJ geometry and MTL materials, read with `craft-mesh` (`craft_mesh::obj`).
//!
//! The material library and the textures it names are read through the caller's resolver: a
//! texture is looked up by the name as written (a name containing spaces is one name), then by
//! its file name (an absolute path from another machine, #1274), then by the final token (the
//! options convention, `-bm 1 file.png`). Faces without normals get smooth normals within a
//! smoothing group and flat ones where smoothing is off.
use crate::scene::{MissingNormals, error, limits, to_model};
use crate::{Model, Result};
pub use craft_mesh::srgb_to_linear;
/// A material reference resolved against the model's folder: the name as written, then its file
/// name, then the final token.
fn resolve_reference(resolve: &dyn Fn(&str) -> Option<Vec<u8>>, name: &str) -> Option<Vec<u8>> {
let mut tried: Vec<&str> = vec![];
for candidate in [name, craft_mesh::obj::sibling_name(name).unwrap_or(name), name.split_whitespace().last().unwrap_or(name)] {
if tried.contains(&candidate) {
continue;
}
tried.push(candidate);
if let Some(b) = resolve(candidate) {
return Some(b);
}
}
None
}
/// Parse OBJ text (and its MTL libraries and textures through `resolve`).
pub fn parse(bytes: &[u8], resolve: &dyn Fn(&str) -> Option<Vec<u8>>) -> Result<Model> {
// `craft-mesh` splits `mtllib` names and tries each complete library path itself. Applying
// texture-option fallback here first can mistake the last library for the whole directive.
let mut library = |name: &str| resolve(name);
let scene = craft_mesh::obj::read(bytes, &limits(), &mut library).map_err(error)?;
to_model(scene, MissingNormals::BySmoothing, &|im| resolve_reference(resolve, im.uri.as_deref()?.trim()))
}