From a033ca948197d5ffcbe48b6668378a1d03bae169 Mon Sep 17 00:00:00 2001 From: casper Date: Tue, 11 May 2021 13:40:33 +0200 Subject: [PATCH] Add volume and area calculations --- src/lib.rs | 61 +++++++++++++++++++++++++++++------ src/testdata/bunny_valid.stl | Bin 0 -> 5284 bytes 2 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 src/testdata/bunny_valid.stl diff --git a/src/lib.rs b/src/lib.rs index 75407cb..c0793f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,6 +168,39 @@ impl IndexedMesh { Ok(()) } } + + /// Calculates the volume of the mesh. In order for a correct volume calculation the mesh must + /// be valid. Use `validate` to make sure that the mesh doesn't contain any holes before + /// calculating the volume. + pub fn volume(&self) -> f32 { + // based on https://doi.org/10.1109/ICIP.2001.958278 + let mut volume = 0.0; + for face in self.faces.iter() { + let a = self.vertices[face.vertices[0]]; + let b = self.vertices[face.vertices[1]]; + let c = self.vertices[face.vertices[2]]; + volume += -c[0] * b[1] * a[2] + + b[0] * c[1] * a[2] + + c[0] * a[1] * b[2] - + a[0] * c[1] * b[2] - + b[0] * a[1] * c[2] + + a[0] * b[1] * c[2]; + } + volume / 6.0 + } + + + /// Calculates the surface area of the mesh. + pub fn area(&self) -> f32 { + let mut area = 0.0; + for face in self.faces.iter() { + let a = self.vertices[face.vertices[0]]; + let b = self.vertices[face.vertices[1]]; + let c = self.vertices[face.vertices[2]]; + area += tri_area(a, b, c); + } + area + } } /// Write to std::io::Write as documented in @@ -545,6 +578,7 @@ mod test { use super::*; const BUNNY_99: &[u8] = include_bytes!("testdata/bunny_99.stl"); const BUNNY_99_ASCII: &[u8] = include_bytes!("testdata/bunny_99_ascii.stl"); + const BUNNY_VALID: &[u8] = include_bytes!("testdata/bunny_valid.stl"); // Will sort the vertices of the Mesh and fix the indices in the faces. fn sort_vertices(mut mesh: super::IndexedMesh) -> super::IndexedMesh { @@ -821,24 +855,31 @@ mod test { } #[test] - fn bunny_tri_area() { + fn bunny_area() { let mut reader = ::std::io::Cursor::new(BUNNY_99); let stl = BinaryStlReader::create_triangle_iterator(&mut reader) .unwrap() .as_indexed_triangles() .unwrap(); - let mut total_area = 0.0; - for face in stl.faces.iter() { - let a = stl.vertices[face.vertices[0]]; - let b = stl.vertices[face.vertices[1]]; - let c = stl.vertices[face.vertices[2]]; - total_area = total_area + tri_area(a, b, c); - } - // area of bunny model according to blender let blender_area: f32 = 0.04998364; - assert!(total_area.approx_eq(blender_area, F32Margin::default())); + assert!(blender_area.approx_eq(stl.area(), F32Margin::default())); } + + #[test] + fn bunny_volume() { + let mut reader = ::std::io::Cursor::new(BUNNY_VALID); + let stl = BinaryStlReader::create_triangle_iterator(&mut reader) + .unwrap() + .as_indexed_triangles() + .unwrap(); + + // volume of bunny model according to blender + let blender_volume: f32 = 690.9882461277; + + assert!(blender_volume.approx_eq(stl.volume(), F32Margin::default())); + } + } diff --git a/src/testdata/bunny_valid.stl b/src/testdata/bunny_valid.stl new file mode 100644 index 0000000000000000000000000000000000000000..8b1fad989882fb1ed77124212ce2ee2f810983e2 GIT binary patch literal 5284 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 WfOB(`?w2`w;dMYNdEmFy82<(TlBh)h literal 0 HcmV?d00001