draco_oxide/io/obj/
mod.rs1use draco_oxide_core::attribute::AttributeDomain;
2use draco_oxide_core::attribute::AttributeType;
4use draco_oxide_core::mesh::builder::MeshBuilder;
5use draco_oxide_core::mesh::Mesh;
6use draco_oxide_core::types::NdVector;
7use std::fmt::Debug;
8use std::path::Path;
9
10#[derive(Debug, thiserror::Error, Clone)]
11pub enum Err {
12 #[error("Mesh Builder Error: {0}")]
13 MeshBuilderError(#[from] draco_oxide_core::mesh::builder::Err),
14}
15
16pub fn load_obj<P: AsRef<Path> + Debug>(path: P) -> Result<Mesh, Err> {
17 let op = tobj::LoadOptions {
18 triangulate: true,
19 single_index: true,
20 ..Default::default()
21 };
22
23 let (models, _materials) = tobj::load_obj(path, &op).expect("Failed to load OBJ file");
24 let model: &tobj::Model = &models[0];
25 let pos = model
26 .mesh
27 .positions
28 .chunks(3)
29 .map(|x| NdVector::from([x[0], x[1], x[2]]))
30 .collect::<Vec<_>>();
31 let faces = model
32 .mesh
33 .indices
34 .chunks(3)
35 .map(|x| [x[0] as usize, x[1] as usize, x[2] as usize])
36 .collect::<Vec<_>>();
37 let (normals, normals_domain_ty) = load_normals(&model.mesh);
38 let (tex_coords, tex_coords_domain_ty) = load_tex_coords(&model.mesh);
39 let mut builder = MeshBuilder::new();
40 builder.set_connectivity_attribute(faces);
41 let pos_att_id = builder.add_attribute(
42 pos,
43 AttributeType::Position,
44 AttributeDomain::Position,
45 vec![],
46 );
47 if !normals.is_empty() {
48 builder.add_attribute(
49 normals,
50 AttributeType::Normal,
51 normals_domain_ty,
52 vec![pos_att_id],
53 );
54 }
55 if !tex_coords.is_empty() {
56 builder.add_attribute(
57 tex_coords,
58 AttributeType::TextureCoordinate,
59 tex_coords_domain_ty,
60 vec![pos_att_id],
61 );
62 }
63
64 Ok(builder.build()?)
65}
66
67fn load_normals(mesh: &tobj::Mesh) -> (Vec<NdVector<3, f32>>, AttributeDomain) {
68 if mesh.normals.is_empty() {
69 return (vec![], AttributeDomain::Position);
70 }
71 let normals = mesh
72 .normals
73 .chunks(3)
74 .map(|x| NdVector::from([x[0], x[1], x[2]]))
75 .collect::<Vec<_>>();
76 (normals, AttributeDomain::Corner)
77}
78
79fn load_tex_coords(mesh: &tobj::Mesh) -> (Vec<NdVector<2, f32>>, AttributeDomain) {
80 if mesh.texcoords.is_empty() {
81 return (vec![], AttributeDomain::Position);
82 }
83 let tex_coords = mesh
84 .texcoords
85 .chunks(2)
86 .map(|x| NdVector::from([x[0], x[1]]))
87 .collect::<Vec<_>>();
88
89 (tex_coords, AttributeDomain::Corner)
90}
91
92#[cfg(test)]
93mod tests {
94 use draco_oxide_core::types::PointIdx;
95
96 use super::*;
97
98 #[test]
99 fn tetrahedron() {
100 let mesh = load_obj("../tests/data/tetrahedron.obj").unwrap();
101 assert_eq!(
102 mesh.get_faces(),
103 vec![
104 [PointIdx::from(0), PointIdx::from(1), PointIdx::from(2)],
105 [PointIdx::from(0), PointIdx::from(3), PointIdx::from(1)],
106 [PointIdx::from(0), PointIdx::from(2), PointIdx::from(4)],
107 [PointIdx::from(1), PointIdx::from(5), PointIdx::from(2)]
108 ]
109 );
110 assert_eq!(mesh.attributes.len(), 3);
111 assert_eq!(
112 mesh.attributes[0].get_attribute_type(),
113 AttributeType::Position
114 );
115 assert_eq!(mesh.attributes[0].get_domain(), AttributeDomain::Position);
116 assert_eq!(mesh.attributes[0].get_num_components(), 3);
117 assert_eq!(mesh.attributes[0].num_unique_values(), 4);
118 assert_eq!(mesh.attributes[0].len(), 6);
119 }
120}