Skip to main content

del_msh_cpu/
io_wavefront_obj.rs

1//! methods for Wavefront Obj files
2
3use anyhow::Context;
4use num_traits::AsPrimitive;
5use std::fs::File;
6use std::io::{BufRead, BufReader, Write};
7use std::ops::AddAssign;
8
9pub struct WavefrontObj<Index, Real> {
10    pub vtx2xyz: Vec<Real>,
11    pub vtx2uv: Vec<Real>,
12    pub vtx2nrm: Vec<Real>,
13    pub elem2idx: Vec<Index>,
14    pub idx2vtx_xyz: Vec<Index>,
15    pub idx2vtx_uv: Vec<Index>,
16    pub idx2vtx_nrm: Vec<Index>,
17    pub elem2group: Vec<Index>,
18    pub group2name: Vec<String>,
19    pub elem2mtl: Vec<Index>,
20    pub mtl_file_name: String,
21    pub mtl2name: Vec<String>,
22}
23
24impl<Index, Real> WavefrontObj<Index, Real>
25where
26    Real: std::str::FromStr + std::fmt::Display + Copy + num_traits::Zero,
27    Index: num_traits::PrimInt + 'static + AddAssign + AsPrimitive<usize> + Copy,
28    usize: AsPrimitive<Index>,
29    i32: AsPrimitive<Index>,
30{
31    pub fn new() -> Self {
32        WavefrontObj::<Index, Real> {
33            vtx2xyz: Vec::new(),
34            vtx2uv: Vec::new(),
35            vtx2nrm: Vec::new(),
36            elem2idx: Vec::new(),
37            idx2vtx_uv: Vec::new(),
38            idx2vtx_nrm: Vec::new(),
39            idx2vtx_xyz: Vec::new(),
40            elem2group: Vec::new(),
41            group2name: Vec::new(),
42            mtl_file_name: "".to_string(),
43            elem2mtl: Vec::new(),
44            mtl2name: Vec::new(),
45        }
46    }
47
48    /// load wavefront obj file into the class
49    pub fn load<P: AsRef<std::path::Path>>(&mut self, filename: P) -> anyhow::Result<()> {
50        let mut elem2vtx_xyz0: Vec<i32> = vec![];
51        let mut elem2vtx_uv0: Vec<i32> = vec![];
52        let mut elem2vtx_nrm0: Vec<i32> = vec![];
53        self.elem2group.clear();
54        self.elem2mtl.clear();
55        self.elem2idx = vec![Index::zero()];
56        let mut name2group = std::collections::BTreeMap::<String, usize>::new();
57        let mut name2mtl = std::collections::BTreeMap::<String, usize>::new();
58        name2group.insert("_default".to_string(), 0);
59        name2mtl.insert("_default".to_string(), 0);
60        let mut i_group = 0_usize;
61        let mut i_mtl = 0_usize;
62        let f = File::open(filename).context("file not found")?;
63        let reader = BufReader::new(f);
64        for line in reader.lines() {
65            let line = line.unwrap();
66            if line.is_empty() {
67                continue;
68            }
69            let char0 = line.chars().next();
70            if char0.is_none() {
71                continue;
72            }
73            let char0 = char0.unwrap();
74            let char1 = line.chars().nth(1);
75            if char1.is_none() {
76                continue;
77            }
78            let char1 = char1.unwrap();
79            if char0 == '#' {
80                continue;
81            }
82            if char0 == 'v' && char1 == ' ' {
83                let v: Vec<&str> = line.split_whitespace().collect();
84                let x = v[1].parse::<Real>().ok().unwrap();
85                let y = v[2].parse::<Real>().ok().unwrap();
86                let z = v[3].parse::<Real>().ok().unwrap();
87                self.vtx2xyz.push(x);
88                self.vtx2xyz.push(y);
89                self.vtx2xyz.push(z);
90            }
91            if char0 == 'g' && char1 == ' ' {
92                let v: Vec<&str> = line.split_whitespace().collect();
93                let name = v[1].to_string();
94                match name2group.get(&name) {
95                    None => {
96                        i_group = name2group.len();
97                        name2group.insert(name, i_group);
98                    }
99                    Some(&v) => {
100                        i_group = v;
101                    }
102                };
103            }
104            if char0 == 'm' && char1 == 't' {
105                let v: Vec<&str> = line.split_whitespace().collect();
106                self.mtl_file_name = v[1].to_string();
107            }
108            if char0 == 'u' && char1 == 's' {
109                let v: Vec<&str> = line.split_whitespace().collect();
110                let name = v[1].to_string();
111                match name2mtl.get(&name) {
112                    None => {
113                        i_mtl = name2mtl.len();
114                        name2mtl.insert(name, i_mtl);
115                    }
116                    Some(&v) => {
117                        i_mtl = v;
118                    }
119                };
120            }
121            if char0 == 'v' && char1 == 'n' {
122                let v: Vec<&str> = line.split_whitespace().collect();
123                let x = v[1].parse::<Real>().ok().unwrap();
124                let y = v[2].parse::<Real>().ok().unwrap();
125                let z = v[3].parse::<Real>().ok().unwrap();
126                self.vtx2nrm.push(x);
127                self.vtx2nrm.push(y);
128                self.vtx2nrm.push(z);
129            }
130            if char0 == 'v' && char1 == 't' {
131                let v: Vec<&str> = line.split_whitespace().collect();
132                let u = v[1].parse::<Real>().ok().unwrap();
133                let v = v[2].parse::<Real>().ok().unwrap();
134                self.vtx2uv.push(u);
135                self.vtx2uv.push(v);
136            }
137            if char0 == 'f' && char1 == ' ' {
138                let v: Vec<&str> = line.split_whitespace().collect();
139                for v_ in v.iter().skip(1) {
140                    // skip first 'f'
141                    let (ipnt, itex, inrm) = parse_vertex(v_);
142                    elem2vtx_xyz0.push(ipnt);
143                    elem2vtx_uv0.push(itex);
144                    elem2vtx_nrm0.push(inrm);
145                }
146                self.elem2idx.push(elem2vtx_xyz0.len().as_());
147                self.elem2group.push(i_group.as_());
148                self.elem2mtl.push(i_mtl.as_());
149            }
150        } // end loop over text
151        self.group2name = vec!["".to_string(); name2group.len()];
152        for (name, &i_group) in name2group.iter() {
153            self.group2name[i_group].clone_from(name);
154        }
155        self.mtl2name = vec!["".to_string(); name2mtl.len()];
156        for (name, &i_mtl) in name2mtl.iter() {
157            self.mtl2name[i_mtl].clone_from(name);
158        }
159        {
160            // fix veretx_xyz index
161            let nvtx_xyz = self.vtx2xyz.len() / 3;
162            self.idx2vtx_xyz = elem2vtx_xyz0
163                .iter()
164                .map(|i| {
165                    if *i >= 0 {
166                        (*i).as_()
167                    } else {
168                        (nvtx_xyz as i32 + *i).as_()
169                    }
170                })
171                .collect();
172        }
173        {
174            // fix veretx_uv index
175            let nvtx_uv = self.vtx2uv.len() / 3;
176            self.idx2vtx_uv = elem2vtx_uv0
177                .iter()
178                .map(|i| {
179                    if *i >= 0 {
180                        (*i).as_()
181                    } else {
182                        (nvtx_uv as i32 + *i).as_()
183                    }
184                })
185                .collect();
186        }
187        {
188            // fix veretx_nrm index
189            let nvtx_nrm = self.vtx2nrm.len() / 3;
190            self.idx2vtx_nrm = elem2vtx_nrm0
191                .iter()
192                .map(|i| {
193                    if *i >= 0 {
194                        (*i).as_()
195                    } else {
196                        (nvtx_nrm as i32 + *i).as_()
197                    }
198                })
199                .collect();
200        }
201        Ok(())
202    }
203
204    pub fn unified_xyz_uv_as_trimesh(&self) -> (Vec<Index>, Vec<Real>, Vec<Real>) {
205        let (tri2uni, uni2vtx_xyz, uni2vtx_uv) =
206            crate::unify_index::unify_two_indices_of_triangle_mesh(
207                &self.idx2vtx_xyz,
208                &self.idx2vtx_uv,
209            );
210        assert_eq!(uni2vtx_xyz.len(), uni2vtx_uv.len());
211        let uni2xyz = crate::map_idx::map_vertex_attibute_from(&self.vtx2xyz, 3, &uni2vtx_xyz);
212        let uni2uv = crate::map_idx::map_vertex_attibute_from(&self.vtx2uv, 2, &uni2vtx_uv);
213        (tri2uni, uni2xyz, uni2uv)
214    }
215}
216
217impl<Index, Real> Default for WavefrontObj<Index, Real>
218where
219    Real: std::str::FromStr + std::fmt::Display + Copy + num_traits::Zero,
220    Index: num_traits::PrimInt + 'static + AddAssign + AsPrimitive<usize> + Copy,
221    usize: AsPrimitive<Index>,
222    i32: AsPrimitive<Index>,
223{
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229pub fn load_tri_mesh<P: AsRef<std::path::Path>, Index, Real>(
230    filepath: P,
231    scale: Option<Real>,
232) -> anyhow::Result<(Vec<Index>, Vec<Real>)>
233where
234    Real: std::str::FromStr + std::fmt::Display + num_traits::Float,
235    Index: num_traits::PrimInt + 'static + AddAssign + AsPrimitive<usize> + Copy,
236    usize: AsPrimitive<Index>,
237    i32: AsPrimitive<Index>,
238{
239    let mut obj = WavefrontObj::<Index, Real>::new();
240    obj.load(&filepath)?;
241    let tri2vtx = obj.idx2vtx_xyz;
242    let mut vtx2xyz = obj.vtx2xyz;
243    if let Some(scale_) = scale {
244        // scale the vertex positions if scale is provided
245        crate::vtx2xyz::normalize_in_place(&mut vtx2xyz, scale_);
246    }
247    Ok((tri2vtx, vtx2xyz))
248}
249
250pub fn save_tri_mesh_texture(
251    filepath: &str,
252    tri2vtx_xyz: &[usize],
253    vtx2xyz: &[f32],
254    tri2vtx_uv: &[usize],
255    vtx2uv: &[f32],
256) -> anyhow::Result<()> {
257    assert_eq!(tri2vtx_xyz.len(), tri2vtx_uv.len());
258    let mut file = File::create(filepath).context("file not found.")?;
259    for i_vtx in 0..vtx2xyz.len() / 3 {
260        writeln!(
261            file,
262            "v {} {} {}",
263            vtx2xyz[i_vtx * 3],
264            vtx2xyz[i_vtx * 3 + 1],
265            vtx2xyz[i_vtx * 3 + 2]
266        )?;
267    }
268    for i_vtx in 0..vtx2uv.len() / 2 {
269        writeln!(file, "vt {} {}", vtx2uv[i_vtx * 2], vtx2uv[i_vtx * 2 + 1])?;
270    }
271    for i_tri in 0..tri2vtx_xyz.len() / 3 {
272        writeln!(
273            file,
274            "f {}/{} {}/{} {}/{}",
275            tri2vtx_xyz[i_tri * 3] + 1,
276            tri2vtx_uv[i_tri * 3] + 1,
277            tri2vtx_xyz[i_tri * 3 + 1] + 1,
278            tri2vtx_uv[i_tri * 3 + 1] + 1,
279            tri2vtx_xyz[i_tri * 3 + 2] + 1,
280            tri2vtx_uv[i_tri * 3 + 2] + 1
281        )?;
282    }
283    Ok(())
284}
285
286fn write_vtx2xyz<Real>(
287    file: &mut std::io::BufWriter<File>,
288    vtx2xyz: &[Real],
289    num_dim: usize,
290) -> anyhow::Result<()>
291where
292    Real: std::fmt::Display,
293{
294    match num_dim {
295        3_usize => {
296            for i_vtx in 0..vtx2xyz.len() / 3 {
297                writeln!(
298                    file,
299                    "v {} {} {}",
300                    vtx2xyz[i_vtx * 3],
301                    vtx2xyz[i_vtx * 3 + 1],
302                    vtx2xyz[i_vtx * 3 + 2]
303                )?;
304            }
305        }
306        2_usize => {
307            for i_vtx in 0..vtx2xyz.len() / 2 {
308                writeln!(
309                    file,
310                    "v {} {} {}",
311                    vtx2xyz[i_vtx * 2],
312                    vtx2xyz[i_vtx * 2 + 1],
313                    0.
314                )?;
315            }
316        }
317        _ => {
318            panic!("dimension should be either 2 or 3");
319        }
320    }
321    Ok(())
322}
323
324fn write_vtx2nrm<Real>(file: &mut std::io::BufWriter<File>, vtx2nrm: &[Real]) -> anyhow::Result<()>
325where
326    Real: std::fmt::Display,
327{
328    for i_vtx in 0..vtx2nrm.len() / 3 {
329        writeln!(
330            file,
331            "vn {} {} {}",
332            vtx2nrm[i_vtx * 3],
333            vtx2nrm[i_vtx * 3 + 1],
334            vtx2nrm[i_vtx * 3 + 2]
335        )?;
336    }
337    Ok(())
338}
339
340fn write_vtx2xyz_vtx2rgb<Real>(
341    file: &mut std::io::BufWriter<File>,
342    vtx2xyz: &[Real],
343    vtx2rgb: &[f32],
344) -> anyhow::Result<()>
345where
346    Real: std::fmt::Display,
347{
348    for i_vtx in 0..vtx2xyz.len() / 3 {
349        writeln!(
350            file,
351            "v {} {} {} {} {} {}",
352            vtx2xyz[i_vtx * 3],
353            vtx2xyz[i_vtx * 3 + 1],
354            vtx2xyz[i_vtx * 3 + 2],
355            vtx2rgb[i_vtx * 3],
356            vtx2rgb[i_vtx * 3 + 1],
357            vtx2rgb[i_vtx * 3 + 2]
358        )?;
359    }
360    Ok(())
361}
362
363fn write_vtx2vecn<Real, const N: usize>(
364    file: &mut std::io::BufWriter<File>,
365    vtx2vecn: &[[Real; N]],
366) -> anyhow::Result<()>
367where
368    Real: num_traits::Float + std::fmt::Display,
369{
370    match N {
371        3_usize => {
372            for vtx in vtx2vecn {
373                writeln!(file, "v {} {} {}", vtx[0], vtx[1], vtx[2])?;
374            }
375        }
376        2_usize => {
377            for vtx in vtx2vecn {
378                writeln!(file, "v {} {} {}", vtx[0], vtx[1], 0.)?;
379            }
380        }
381        _ => {
382            panic!();
383        }
384    }
385    Ok(())
386}
387
388pub fn save_tri2vtx_vtx2xyz<Path, Index, Real>(
389    filepath: Path,
390    tri2vtx: &[Index],
391    vtx2xyz: &[Real],
392    num_dim: usize,
393) -> anyhow::Result<()>
394where
395    Path: AsRef<std::path::Path>,
396    Real: num_traits::Float + std::fmt::Display,
397    Index: num_traits::PrimInt + std::fmt::Display,
398{
399    let file = File::create(&filepath)
400        .context(format!("file not found. {}", filepath.as_ref().display()))?;
401    let mut file = std::io::BufWriter::new(file);
402    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
403    for i_tri in 0..tri2vtx.len() / 3 {
404        writeln!(
405            file,
406            "f {} {} {}",
407            tri2vtx[i_tri * 3] + Index::one(),
408            tri2vtx[i_tri * 3 + 1] + Index::one(),
409            tri2vtx[i_tri * 3 + 2] + Index::one()
410        )?;
411    }
412    Ok(())
413}
414
415pub fn save_tri2vtx_vtx2xyz_vtx2rgb<Path, Index, Real>(
416    filepath: Path,
417    tri2vtx: &[Index],
418    vtx2xyz: &[Real],
419    vtx2rgb: &[f32],
420) -> anyhow::Result<()>
421where
422    Path: AsRef<std::path::Path>,
423    Real: num_traits::Float + std::fmt::Display,
424    Index: num_traits::PrimInt + std::fmt::Display,
425{
426    let file = File::create(filepath).context("file not found.")?;
427    let mut file = std::io::BufWriter::new(file);
428    write_vtx2xyz_vtx2rgb(&mut file, vtx2xyz, vtx2rgb)?;
429    for i_tri in 0..tri2vtx.len() / 3 {
430        writeln!(
431            file,
432            "f {} {} {}",
433            tri2vtx[i_tri * 3] + Index::one(),
434            tri2vtx[i_tri * 3 + 1] + Index::one(),
435            tri2vtx[i_tri * 3 + 2] + Index::one()
436        )?;
437    }
438    Ok(())
439}
440
441pub fn save_tri2vtx_vtx2xyz_vtx2nrm<Path, Index, Real>(
442    filepath: Path,
443    tri2vtx: &[Index],
444    vtx2xyz: &[Real],
445    vtx2nrm: &[Real],
446) -> anyhow::Result<()>
447where
448    Path: AsRef<std::path::Path>,
449    Real: num_traits::Float + std::fmt::Display,
450    Index: num_traits::PrimInt + std::fmt::Display,
451{
452    let file = File::create(filepath).context("file not found.")?;
453    let mut file = std::io::BufWriter::new(file);
454    write_vtx2xyz(&mut file, vtx2xyz, 3)?;
455    write_vtx2nrm(&mut file, vtx2nrm)?;
456    for i_tri in 0..tri2vtx.len() / 3 {
457        let i0 = tri2vtx[i_tri * 3] + Index::one();
458        let i1 = tri2vtx[i_tri * 3 + 1] + Index::one();
459        let i2 = tri2vtx[i_tri * 3 + 2] + Index::one();
460        writeln!(file, "f {i0}//{i0} {i1}//{i1} {i2}//{i2}")?;
461    }
462    Ok(())
463}
464
465pub fn save_tri2vtx_vtx2vecn<Path, Real, const N: usize>(
466    filepath: Path,
467    tri2vtx: &[usize],
468    vtx2vecn: &[[Real; N]],
469) -> anyhow::Result<()>
470where
471    Path: AsRef<std::path::Path>,
472    Real: num_traits::Float + std::fmt::Display,
473{
474    let file = File::create(filepath).context("file not found.")?;
475    let mut file = std::io::BufWriter::new(file);
476    write_vtx2vecn(&mut file, vtx2vecn)?;
477    for tri in tri2vtx.chunks(3) {
478        writeln!(file, "f {} {} {}", tri[0] + 1, tri[1] + 1, tri[2] + 1)?;
479    }
480    Ok(())
481}
482
483pub fn save_vtx2xyz_as_polyloop<Path, Real>(
484    filepath: Path,
485    vtx2xyz: &[Real],
486    num_dim: usize,
487) -> anyhow::Result<()>
488where
489    Path: AsRef<std::path::Path>,
490    Real: num_traits::Float + std::fmt::Display,
491{
492    let file = File::create(filepath).context("file not found.")?;
493    let mut file = std::io::BufWriter::new(file);
494    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
495    let num_vtx = vtx2xyz.len() / num_dim;
496    for i_vtx in 0..num_vtx {
497        let i0 = i_vtx;
498        let i1 = (i_vtx + 1) % num_vtx;
499        writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
500    }
501    Ok(())
502}
503
504pub fn save_vtx2vecn_as_polyloop<Path, Real, const N: usize>(
505    filepath: Path,
506    vtx2vecn: &[[Real; N]],
507) -> anyhow::Result<()>
508where
509    Path: AsRef<std::path::Path>,
510    Real: num_traits::Float + std::fmt::Display,
511{
512    let file = File::create(filepath).context("file not found.")?;
513    let mut file = std::io::BufWriter::new(file);
514    write_vtx2vecn(&mut file, vtx2vecn)?;
515    let num_vtx = vtx2vecn.len();
516    for i_vtx in 0..num_vtx {
517        let i0 = i_vtx;
518        let i1 = (i_vtx + 1) % num_vtx;
519        writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
520    }
521    Ok(())
522}
523
524// ------------------------
525
526pub fn save_vtx2xyz_as_polyline<Path, Real>(
527    filepath: Path,
528    vtx2xyz: &[Real],
529    num_dim: usize,
530) -> anyhow::Result<()>
531where
532    Path: AsRef<std::path::Path>,
533    Real: num_traits::Float + std::fmt::Display,
534{
535    let file = File::create(filepath).context("file not found.")?;
536    let mut file = std::io::BufWriter::new(file);
537    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
538    let num_vtx = vtx2xyz.len() / num_dim;
539    for i_vtx in 0..num_vtx - 1 {
540        let i0 = i_vtx;
541        let i1 = i_vtx + 1;
542        writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
543    }
544    Ok(())
545}
546
547// ------------------------
548
549pub fn save_edge2vtx_vtx2xyz<Path, Real>(
550    filepath: Path,
551    edge2vtx: &[usize],
552    vtx2xyz: &[Real],
553    num_dim: usize,
554) -> anyhow::Result<()>
555where
556    Path: AsRef<std::path::Path>,
557    Real: num_traits::Float + std::fmt::Display,
558{
559    let file = File::create(filepath).context("file  not found.")?;
560    let mut file = std::io::BufWriter::new(file);
561    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
562    // let num_vtx = vtx2xyz.len() / num_dim;
563    for node2vtx in edge2vtx.chunks(2) {
564        let (i0, i1) = (node2vtx[0], node2vtx[1]);
565        writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
566    }
567    Ok(())
568}
569
570pub fn save_polyline2vtx_vtx2xyz<Path, Real>(
571    filepath: Path,
572    polyline2vtx: &[usize],
573    vtx2xyz: &[Real],
574    num_dim: usize,
575) -> anyhow::Result<()>
576where
577    Path: AsRef<std::path::Path>,
578    Real: num_traits::Float + std::fmt::Display,
579{
580    let file = File::create(filepath).context("file  not found.")?;
581    let mut file = std::io::BufWriter::new(file);
582    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
583    // let num_vtx = vtx2xyz.len() / num_dim;
584    for i_poly in 0..polyline2vtx.len() - 1 {
585        let num_vtx_in_polyline = polyline2vtx[i_poly + 1] - polyline2vtx[i_poly];
586        for i_vtx in 0..num_vtx_in_polyline - 1 {
587            let i0 = polyline2vtx[i_poly] + i_vtx;
588            let i1 = polyline2vtx[i_poly] + i_vtx + 1;
589            writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
590        }
591    }
592    Ok(())
593}
594
595pub fn save_quad2vtx_vtx2xyz<Path, Real>(
596    filepath: Path,
597    quad2vtx: &[usize],
598    vtx2xyz: &[Real],
599    num_dim: usize,
600) -> anyhow::Result<()>
601where
602    Path: AsRef<std::path::Path>,
603    Real: num_traits::Float + std::fmt::Display,
604{
605    let file = File::create(filepath).context("file  not found.")?;
606    let mut file = std::io::BufWriter::new(file);
607    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
608    // let num_vtx = vtx2xyz.len() / num_dim;
609    for node2vtx in quad2vtx.chunks(4) {
610        let i0 = node2vtx[0];
611        let i1 = node2vtx[1];
612        let i2 = node2vtx[2];
613        let i3 = node2vtx[3];
614        writeln!(file, "f {} {} {} {}", i0 + 1, i1 + 1, i2 + 1, i3 + 1)?;
615    }
616    Ok(())
617}
618
619pub fn save_elem2idx_idx2vtx_vtx2xyz<Path, Real>(
620    filepath: Path,
621    elem2idx: &[usize],
622    idx2vtx: &[usize],
623    vtx2xyz: &[Real],
624    num_dim: usize,
625) -> anyhow::Result<()>
626where
627    Path: AsRef<std::path::Path>,
628    Real: num_traits::Float + std::fmt::Display,
629{
630    let file = File::create(filepath).context("file  not found.")?;
631    let mut file = std::io::BufWriter::new(file);
632    write_vtx2xyz(&mut file, vtx2xyz, num_dim)?;
633    // let num_vtx = vtx2xyz.len() / num_dim;
634    for i_elem in 0..elem2idx.len() - 1 {
635        let noel = &idx2vtx[elem2idx[i_elem]..elem2idx[i_elem + 1]];
636        match noel.len() {
637            3 => writeln!(file, "f {} {} {}", noel[0] + 1, noel[1] + 1, noel[2] + 1)?,
638            4 => writeln!(
639                file,
640                "f {} {} {} {}",
641                noel[0] + 1,
642                noel[1] + 1,
643                noel[2] + 1,
644                noel[3] + 1
645            )?,
646            _ => {
647                unreachable!()
648            }
649        }
650    }
651    Ok(())
652}
653
654// ------------------------/
655
656pub fn save_tri2xyz<Path, Real>(filepath: Path, tri2xyz: &[Real]) -> anyhow::Result<()>
657where
658    Path: AsRef<std::path::Path>,
659    Real: num_traits::Float + std::fmt::Display,
660{
661    let file = File::create(filepath).context("file  not found.")?;
662    let mut file = std::io::BufWriter::new(file);
663    write_vtx2xyz(&mut file, tri2xyz, 3)?;
664    // let num_vtx = vtx2xyz.len() / num_dim;
665    let num_tri = tri2xyz.len() / 9;
666    for i_tri in 0..num_tri {
667        writeln!(
668            file,
669            "f {} {} {}",
670            i_tri * 3 + 1,
671            i_tri * 3 + 2,
672            i_tri * 3 + 3
673        )?;
674    }
675    Ok(())
676}
677
678// -------------------------
679// below: private functions
680
681fn parse_vertex(str_in: &str) -> (i32, i32, i32) {
682    let snums: Vec<&str> = str_in.split('/').collect();
683    let mut nums: [i32; 3] = [0, 0, 0];
684    for i in 0..snums.len() {
685        nums[i] = snums[i].parse::<i32>().unwrap_or(0);
686    }
687    (nums[0] - 1, nums[1] - 1, nums[2] - 1)
688}
689
690#[test]
691fn test_parse_vertex() {
692    assert_eq!(parse_vertex("1/2/3"), (0, 1, 2));
693    assert_eq!(parse_vertex("1//3"), (0, -1, 2));
694    assert_eq!(parse_vertex("1/2"), (0, 1, -1));
695    assert_eq!(parse_vertex("1"), (0, -1, -1));
696}