1use crate::{
2 Handle, HasIterators, HasTopology, VH, VPropBuf,
3 error::Error,
4 mesh::{Adaptor, FloatScalarAdaptor, PolyMeshT},
5};
6use std::{cell::Ref, fmt::Display, fs::OpenOptions, io, path::Path};
7
8impl<A> PolyMeshT<3, A>
9where
10 A: Adaptor<3> + FloatScalarAdaptor<3>,
11{
12 fn load_from_models(models: Vec<tobj::Model>) -> Result<Self, Error> {
13 let (nverts, nfaces) = models
14 .iter()
15 .fold((0usize, 0usize), |(nverts, nfaces), model| {
16 let msh = &model.mesh;
17 (
18 nverts + (msh.positions.len() / 3),
19 nfaces + msh.face_arities.len(),
20 )
21 });
22 let nedges = nfaces * 3 / 2; let mut outmesh = PolyMeshT::<3, A>::with_capacity(nverts, nedges, nfaces);
24 let mut positions = Vec::new();
25 let mut fvs: Vec<VH> = Vec::new();
26 for model in models {
27 let mesh = &model.mesh;
28 if mesh.positions.len() % 3 != 0 {
29 return Err(Error::IncorrectNumberOfCoordinates(mesh.positions.len()));
30 }
31 positions.clear();
32 positions.extend(mesh.positions.chunks(3).map(|triplet| {
33 A::vector([
34 A::scalarf64(triplet[0]),
35 A::scalarf64(triplet[1]),
36 A::scalarf64(triplet[2]),
37 ])
38 }));
39 let nbefore = outmesh.num_vertices() as u32;
40 let vertices = outmesh.add_vertices(&positions)?;
41 assert_eq!(
42 vertices,
43 (nbefore..(nbefore + positions.len() as u32)).into(),
44 "Vertex indices are expected to be in a contiguous range."
45 );
46 let mut start = 0usize;
48 if mesh.face_arities.is_empty() {
49 for indices in mesh.indices.chunks(3) {
51 fvs.clear();
52 fvs.extend(indices.iter().map(|i| -> VH { i.into() }));
53 outmesh.add_face(&fvs)?;
54 }
55 } else {
56 for size in &mesh.face_arities {
57 let size = *size as usize;
58 let indices = &mesh.indices[start..(start + size)];
59 start += size;
60 fvs.clear();
61 fvs.extend(indices.iter().map(|i| -> VH { i.into() }));
62 outmesh.add_face(&fvs)?;
63 }
64 }
65 }
66 Ok(outmesh)
67 }
68
69 pub fn load_obj<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
71 let path: &Path = path.as_ref();
72 if path
73 .extension()
74 .ok_or(Error::InvalidObjFile(path.to_path_buf()))?
75 .to_str()
76 .ok_or(Error::InvalidObjFile(path.to_path_buf()))?
77 != "obj"
78 {
79 return Err(Error::InvalidObjFile(path.to_path_buf()));
80 }
81 let options = tobj::LoadOptions::default();
82 let (models, _) =
83 tobj::load_obj(path, &options).map_err(|e| Error::ObjLoadFailed(format!("{e}")))?;
84 Self::load_from_models(models)
85 }
86
87 pub fn read_obj(reader: &mut impl io::BufRead) -> Result<Self, Error> {
89 let options = tobj::LoadOptions::default();
90 let (models, _) = tobj::load_obj_buf(reader, &options, |_| {
91 tobj::MTLLoadResult::Ok(Default::default())
92 })
93 .map_err(|e| Error::ObjLoadFailed(format!("{e}")))?;
94 Self::load_from_models(models)
95 }
96
97 fn write_obj_impl(
98 &self,
99 points: Ref<VPropBuf<A::Vector>>,
100 vnormals: Option<Ref<VPropBuf<A::Vector>>>,
101 mut w: impl io::Write,
102 ) -> Result<(), io::Error>
103 where
104 A::Scalar: Display,
105 {
106 writeln!(w, "# {} vertices", self.num_vertices())?;
107 for pos in points.iter() {
108 writeln!(
109 w,
110 "v {} {} {}",
111 A::vector_coord(pos, 0),
112 A::vector_coord(pos, 1),
113 A::vector_coord(pos, 2)
114 )?;
115 }
116 if let Some(vnormals) = vnormals {
117 writeln!(w, "# {} normals", vnormals.len())?;
118 for n in vnormals.iter() {
119 writeln!(
120 w,
121 "vn {} {} {}",
122 A::vector_coord(n, 0),
123 A::vector_coord(n, 1),
124 A::vector_coord(n, 2)
125 )?;
126 }
127 }
128 writeln!(w, "# {} faces", self.num_faces())?;
129 for f in self.faces() {
130 write!(w, "f")?;
131 for v in self.fv_ccw_iter(f) {
132 write!(w, " {}", v.index() + 1)?;
133 }
134 writeln!(w)?;
135 }
136 Ok(())
137 }
138
139 pub fn write_obj(&self, out: &mut impl io::Write) -> Result<(), Error>
140 where
141 A::Scalar: Display,
142 {
143 self.check_for_deleted()?;
144 self.check_topology()?;
145 let points = self.points();
146 let vnormals = self.vertex_normals();
147 self.write_obj_impl(
149 points.try_borrow()?,
150 match &vnormals {
151 Some(n) => Some(n.try_borrow()?),
152 None => None,
153 },
154 out,
155 )
156 .map_err(|_| Error::CannotWriteOBJ)?;
157 Ok(())
158 }
159
160 pub fn save_obj<P: AsRef<Path>>(&self, path: P) -> Result<(), Error>
162 where
163 A::Scalar: Display,
164 {
165 let path = path.as_ref();
166 if path
167 .extension()
168 .ok_or(Error::InvalidObjFile(path.to_path_buf()))?
169 .to_str()
170 .ok_or(Error::InvalidObjFile(path.to_path_buf()))?
171 != "obj"
172 {
173 return Err(Error::InvalidObjFile(path.to_path_buf()));
174 }
175 let file = OpenOptions::new()
176 .write(true)
177 .truncate(true)
178 .create(true)
179 .open(path)
180 .map_err(|_| Error::CannotOpenFile(path.to_path_buf()))?;
181 let mut writer = io::BufWriter::new(&file);
182 self.write_obj(&mut writer)
183 }
184}
185
186#[cfg(test)]
187pub(crate) mod test {
188 use crate::{HasTopology, mesh::PolyMeshF32};
189 use core::str;
190 use std::{io, path::PathBuf};
191
192 pub(crate) fn bunny_mesh() -> PolyMeshF32 {
193 let path = {
194 let mut dirpath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
195 dirpath.push("assets");
196 dirpath.push("bunny.obj");
197 dirpath
198 };
199 let mesh = PolyMeshF32::load_obj(&path).expect("Cannot load mesh");
200 let mut points = mesh.points();
201 let mut points = points.try_borrow_mut().expect("Cannot borrow points");
202 for p in points.iter_mut() {
203 *p *= 10.0;
204 }
205 mesh
206 }
207
208 fn large_bunny_mesh() -> PolyMeshF32 {
209 let path = {
210 let mut dirpath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
211 dirpath.push("assets");
212 dirpath.push("bunny_large.obj");
213 dirpath
214 };
215 PolyMeshF32::load_obj(&path).expect("Cannot load mesh")
216 }
217
218 #[test]
219 fn t_bunny_topol() {
220 let mesh = bunny_mesh();
221 assert_eq!(2503, mesh.num_vertices());
222 assert_eq!(7473, mesh.num_edges());
223 assert_eq!(4968, mesh.num_faces());
224 assert_eq!(42, mesh.edges().filter(|e| e.is_boundary(&mesh)).count());
225 }
226
227 #[test]
228 fn t_bunny_area() {
229 let mesh = bunny_mesh();
230 assert_eq!(
231 mesh.try_calc_area().expect("Unable to compute the area"),
232 5.6468635
233 );
234 }
235
236 #[test]
237 fn t_bunny_volume() {
238 let mesh = bunny_mesh(); assert_eq!(mesh.try_calc_volume().expect("Cannot compute volume"), 0.);
240 }
241
242 #[test]
243 fn t_large_bunny_volume() {
244 let mesh = large_bunny_mesh();
245 assert_eq!(
246 mesh.try_calc_volume().expect("Cannot compute volume"),
247 6.039229
248 );
249 }
250
251 #[test]
252 fn t_bunny_topology_check() {
253 bunny_mesh()
254 .check_topology()
255 .expect("Found topological errors");
256 }
257
258 #[test]
259 fn t_large_bunny_topology_check() {
260 large_bunny_mesh()
261 .check_topology()
262 .expect("Found topological errors");
263 }
264
265 #[test]
266 fn t_quad_bunny() {
267 let path = {
268 let mut dirpath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
269 dirpath.push("assets");
270 dirpath.push("bunny_quad.obj");
271 dirpath
272 };
273 let mesh = PolyMeshF32::load_obj(path).expect("Cannot load mesh from obj");
274 mesh.check_topology().expect("Topological errors found");
275 }
276
277 #[test]
278 fn t_write_obj_to_string() {
279 let mesh = PolyMeshF32::hexahedron(1.0).expect("Cannot make a box");
280 let mut writer = io::BufWriter::new(Vec::new());
281 mesh.write_obj(&mut writer)
282 .expect("Cannot write obj to string");
283 assert_eq!(
284 "
285# 8 vertices
286v -0.57735026 -0.57735026 -0.57735026
287v 0.57735026 -0.57735026 -0.57735026
288v 0.57735026 0.57735026 -0.57735026
289v -0.57735026 0.57735026 -0.57735026
290v -0.57735026 -0.57735026 0.57735026
291v 0.57735026 -0.57735026 0.57735026
292v 0.57735026 0.57735026 0.57735026
293v -0.57735026 0.57735026 0.57735026
294# 6 faces
295f 4 3 2 1
296f 3 7 6 2
297f 6 7 8 5
298f 1 5 8 4
299f 4 8 7 3
300f 2 6 5 1
301"
302 .trim(),
303 str::from_utf8(writer.buffer())
304 .expect("Cannot decode utf8 string")
305 .trim()
306 );
307 }
308
309 #[test]
310 fn t_obj_string_round_trip() {
311 let meshin = PolyMeshF32::hexahedron(1.0).expect("Cannot make a box");
312 let mut writer = io::BufWriter::new(Vec::new());
313 meshin
314 .write_obj(&mut writer)
315 .expect("Cannot write obj to string");
316 let mut bytes = writer.buffer();
317 let meshout = PolyMeshF32::read_obj(&mut bytes).expect("Cannot read mesh from obj string");
318 assert_eq!(meshin.num_vertices(), meshout.num_vertices());
319 assert_eq!(meshin.num_edges(), meshout.num_edges());
320 assert_eq!(meshin.num_faces(), meshout.num_faces());
321 assert_eq!(
322 meshin.try_calc_area().expect("Cannot compute area"),
323 meshout.try_calc_area().expect("Cannot compute area")
324 );
325 assert_eq!(
326 meshin.try_calc_volume().expect("Cannot compute volume"),
327 meshout.try_calc_volume().expect("Cannot compute volume")
328 );
329 assert_eq!(
330 meshin.try_calc_vertex_centroid().unwrap(),
331 meshout.try_calc_vertex_centroid().unwrap()
332 );
333 assert_eq!(
334 meshin.try_calc_area_centroid().unwrap(),
335 meshout.try_calc_area_centroid().unwrap()
336 );
337 }
338}