1use 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 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 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 } 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 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 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 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 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
524pub 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
547pub 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 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 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 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 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
654pub 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_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
678fn 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}