mod area;
mod bounding_box;
mod edge_length;
pub(crate) mod helpers;
mod volume;
pub use area::{face_area, solid_surface_area};
pub(crate) use bounding_box::face_set_bounding_box;
pub use bounding_box::solid_bounding_box;
pub use edge_length::{edge_length, face_perimeter, wire_length};
pub use volume::{
oriented_solid_volume, solid_center_of_mass, solid_volume, solid_volume_from_faces,
};
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic)]
use brepkit_math::tolerance::Tolerance;
use brepkit_topology::Topology;
use brepkit_topology::face::FaceSurface;
use brepkit_topology::test_utils::make_unit_cube_non_manifold;
use super::*;
fn assert_rel(actual: f64, expected: f64, rel_tol: f64, label: &str) {
let rel_err = if expected.abs() < 1e-15 {
actual.abs()
} else {
(actual - expected).abs() / expected.abs()
};
assert!(
rel_err < rel_tol,
"{label}: expected {expected:.8}, got {actual:.8}, \
rel_err={rel_err:.2e} (tolerance={rel_tol:.0e})"
);
}
fn assert_aabb_contains(
aabb: &brepkit_math::aabb::Aabb3,
min_x: f64,
min_y: f64,
min_z: f64,
max_x: f64,
max_y: f64,
max_z: f64,
) {
let slack = 1e-6;
assert!(
aabb.min.x() <= min_x + slack,
"min.x={} > {min_x}",
aabb.min.x()
);
assert!(
aabb.min.y() <= min_y + slack,
"min.y={} > {min_y}",
aabb.min.y()
);
assert!(
aabb.min.z() <= min_z + slack,
"min.z={} > {min_z}",
aabb.min.z()
);
assert!(
aabb.max.x() >= max_x - slack,
"max.x={} < {max_x}",
aabb.max.x()
);
assert!(
aabb.max.y() >= max_y - slack,
"max.y={} < {max_y}",
aabb.max.y()
);
assert!(
aabb.max.z() >= max_z - slack,
"max.z={} < {max_z}",
aabb.max.z()
);
}
#[test]
fn unit_cube_bounding_box() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let aabb = solid_bounding_box(&topo, solid).unwrap();
let tol = Tolerance::new();
assert!(tol.approx_eq(aabb.min.x(), 0.0));
assert!(tol.approx_eq(aabb.min.y(), 0.0));
assert!(tol.approx_eq(aabb.min.z(), 0.0));
assert!(tol.approx_eq(aabb.max.x(), 1.0));
assert!(tol.approx_eq(aabb.max.y(), 1.0));
assert!(tol.approx_eq(aabb.max.z(), 1.0));
}
#[test]
fn sphere_bounding_box() {
use crate::primitives::make_sphere;
let mut topo = Topology::new();
let solid = make_sphere(&mut topo, 5.0, 8).unwrap();
let aabb = solid_bounding_box(&topo, solid).unwrap();
assert_aabb_contains(&aabb, -5.0, -5.0, -5.0, 5.0, 5.0, 5.0);
}
#[test]
fn cylinder_bounding_box() {
use crate::primitives::make_cylinder;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 3.0, 10.0).unwrap();
let aabb = solid_bounding_box(&topo, solid).unwrap();
assert_aabb_contains(&aabb, -3.0, -3.0, 0.0, 3.0, 3.0, 10.0);
}
#[test]
fn torus_bounding_box() {
use crate::primitives::make_torus;
let mut topo = Topology::new();
let solid = make_torus(&mut topo, 10.0, 3.0, 16).unwrap();
let aabb = solid_bounding_box(&topo, solid).unwrap();
assert!(aabb.min.x() <= -13.0 + 1e-6, "min.x={}", aabb.min.x());
assert!(aabb.min.y() <= -13.0 + 1e-6, "min.y={}", aabb.min.y());
assert!(aabb.max.x() >= 13.0 - 1e-6, "max.x={}", aabb.max.x());
assert!(aabb.max.y() >= 13.0 - 1e-6, "max.y={}", aabb.max.y());
assert!(aabb.min.z() <= -3.0 + 1e-6, "min.z={}", aabb.min.z());
assert!(aabb.max.z() >= 3.0 - 1e-6, "max.z={}", aabb.max.z());
}
#[test]
fn unit_cube_volume() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let vol = solid_volume(&topo, solid, 0.1).unwrap();
assert_rel(vol, 1.0, 1e-8, "unit cube volume");
}
#[test]
fn box_volume_exact() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 10.0, 10.0, 10.0).unwrap();
let vol = solid_volume(&topo, solid, 0.1).unwrap();
assert_rel(vol, 1000.0, 1e-8, "10x10x10 box volume");
}
#[test]
fn box_fuse_volume_exact_via_planar_path() {
use crate::boolean::{BooleanOp, boolean};
use brepkit_math::mat::Mat4;
let mut topo = Topology::new();
let a = crate::primitives::make_box(&mut topo, 10.0, 10.0, 10.0).unwrap();
let b = crate::primitives::make_box(&mut topo, 10.0, 10.0, 10.0).unwrap();
crate::transform::transform_solid(&mut topo, b, &Mat4::translation(5.0, 5.0, 5.0)).unwrap();
let fused = boolean(&mut topo, BooleanOp::Fuse, a, b).unwrap();
let vol = solid_volume(&topo, fused, 0.1).unwrap();
assert_rel(vol, 1875.0, 1e-9, "overlapping box fuse volume");
}
#[test]
fn box_through_hole_volume_exact_via_planar_path() {
use crate::boolean::{BooleanOp, boolean};
use brepkit_math::mat::Mat4;
let mut topo = Topology::new();
let a = crate::primitives::make_box(&mut topo, 10.0, 10.0, 10.0).unwrap();
let tool = crate::primitives::make_box(&mut topo, 2.0, 2.0, 12.0).unwrap();
crate::transform::transform_solid(&mut topo, tool, &Mat4::translation(4.0, 4.0, -1.0))
.unwrap();
let cut = boolean(&mut topo, BooleanOp::Cut, a, tool).unwrap();
let vol = solid_volume(&topo, cut, 0.1).unwrap();
assert_rel(vol, 960.0, 1e-9, "box through-hole volume");
}
#[test]
fn rectangular_box_volume_exact() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 2.5, 7.3, 4.1).unwrap();
let vol = solid_volume(&topo, solid, 0.1).unwrap();
assert_rel(vol, 2.5 * 7.3 * 4.1, 1e-8, "2.5x7.3x4.1 box volume");
}
#[test]
fn sphere_volume_analytic_exact() {
use crate::primitives::make_sphere;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_sphere(&mut topo, 3.0, 4).unwrap();
let vol = solid_volume(&topo, solid, 0.5).unwrap();
let expected = 4.0 / 3.0 * PI * 27.0;
assert_rel(vol, expected, 1e-10, "sphere r=3 volume");
}
#[test]
fn cylinder_volume_analytic_exact() {
use crate::primitives::make_cylinder;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 5.0, 20.0).unwrap();
let vol = solid_volume(&topo, solid, 0.5).unwrap();
let expected = PI * 25.0 * 20.0;
assert_rel(vol, expected, 1e-10, "cylinder r=5 h=20 volume");
}
#[test]
fn cone_pointed_volume_analytic_exact() {
use crate::primitives::make_cone;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cone(&mut topo, 5.0, 0.0, 15.0).unwrap();
let vol = solid_volume(&topo, solid, 0.5).unwrap();
let expected = PI / 3.0 * 25.0 * 15.0;
assert_rel(vol, expected, 1e-10, "pointed cone r=5 h=15 volume");
}
#[test]
fn cone_frustum_volume_analytic_exact() {
use crate::primitives::make_cone;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cone(&mut topo, 2.0, 1.0, 3.0).unwrap();
let vol = solid_volume(&topo, solid, 0.5).unwrap();
let expected = PI / 3.0 * 3.0 * (4.0 + 2.0 + 1.0);
assert_rel(vol, expected, 1e-10, "frustum r1=2 r2=1 h=3 volume");
}
#[test]
fn torus_volume_analytic_exact() {
use crate::primitives::make_torus;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_torus(&mut topo, 10.0, 3.0, 32).unwrap();
let vol = solid_volume(&topo, solid, 0.5).unwrap();
let expected = 2.0 * PI * PI * 10.0 * 9.0;
assert_rel(vol, expected, 1e-10, "torus R=10 r=3 volume");
}
#[test]
fn ellipsoid_volume() {
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = crate::primitives::make_sphere(&mut topo, 1.0, 16).unwrap();
let mat = brepkit_math::mat::Mat4::scale(5.0, 3.0, 2.0);
crate::transform::transform_solid(&mut topo, solid, &mat).unwrap();
let vol = solid_volume(&topo, solid, 0.01).unwrap();
let expected = 4.0 / 3.0 * PI * 5.0 * 3.0 * 2.0;
assert_rel(vol, expected, 0.01, "ellipsoid 5x3x2 volume");
}
#[test]
fn extruded_box_volume() {
use brepkit_math::vec::{Point3, Vec3 as V};
use brepkit_topology::edge::{Edge, EdgeCurve};
use brepkit_topology::face::{Face, FaceSurface};
use brepkit_topology::vertex::Vertex;
use brepkit_topology::wire::{OrientedEdge, Wire};
let mut topo = Topology::new();
let v0 = topo.add_vertex(Vertex::new(Point3::new(0.0, 0.0, 0.0), 1e-10));
let v1 = topo.add_vertex(Vertex::new(Point3::new(2.0, 0.0, 0.0), 1e-10));
let v2 = topo.add_vertex(Vertex::new(Point3::new(2.0, 3.0, 0.0), 1e-10));
let v3 = topo.add_vertex(Vertex::new(Point3::new(0.0, 3.0, 0.0), 1e-10));
let e0 = topo.add_edge(Edge::new(v0, v1, EdgeCurve::Line));
let e1 = topo.add_edge(Edge::new(v1, v2, EdgeCurve::Line));
let e2 = topo.add_edge(Edge::new(v2, v3, EdgeCurve::Line));
let e3 = topo.add_edge(Edge::new(v3, v0, EdgeCurve::Line));
let wire = Wire::new(
vec![
OrientedEdge::new(e0, true),
OrientedEdge::new(e1, true),
OrientedEdge::new(e2, true),
OrientedEdge::new(e3, true),
],
true,
)
.unwrap();
let wid = topo.add_wire(wire);
let face_id = topo.add_face(Face::new(
wid,
vec![],
FaceSurface::Plane {
normal: V::new(0.0, 0.0, 1.0),
d: 0.0,
},
));
let solid =
crate::extrude::extrude(&mut topo, face_id, V::new(0.0, 0.0, 1.0), 4.0).unwrap();
let vol = solid_volume(&topo, solid, 0.1).unwrap();
assert_rel(vol, 24.0, 1e-8, "extruded 2x3x4 box volume");
}
#[test]
fn fillet_single_edge_volume() {
use brepkit_topology::explorer;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 20.0, 20.0, 20.0).unwrap();
let edges: Vec<_> = explorer::solid_edges(&topo, solid).unwrap();
#[allow(deprecated)]
let filleted =
crate::fillet::fillet_rolling_ball(&mut topo, solid, &[edges[0]], 2.0).unwrap();
let vol = solid_volume(&topo, filleted, 0.01).unwrap();
let expected = 8000.0 - (1.0 - PI / 4.0) * 4.0 * 20.0;
assert_rel(vol, expected, 0.01, "fillet r=2 on 20^3 box, one edge");
}
#[test]
fn unit_cube_surface_area() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let area = solid_surface_area(&topo, solid, 0.1).unwrap();
assert_rel(area, 6.0, 1e-8, "unit cube surface area");
}
#[test]
fn unit_cube_face_area() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let solid_data = topo.solid(solid).unwrap();
let shell = topo.shell(solid_data.outer_shell()).unwrap();
for &fid in shell.faces() {
let area = face_area(&topo, fid, 0.1).unwrap();
assert_rel(area, 1.0, 1e-8, "unit cube face area");
}
}
#[test]
fn box_surface_area_exact() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 10.0, 10.0, 10.0).unwrap();
let area = solid_surface_area(&topo, solid, 0.1).unwrap();
assert_rel(area, 600.0, 1e-8, "10^3 box surface area");
}
#[test]
fn cylinder_surface_area() {
use crate::primitives::make_cylinder;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 3.0, 10.0).unwrap();
let area = solid_surface_area(&topo, solid, 0.01).unwrap();
let expected = 2.0 * PI * (9.0 + 30.0);
assert_rel(area, expected, 1e-4, "cylinder r=3 h=10 surface area");
}
#[test]
fn sphere_surface_area() {
use crate::primitives::make_sphere;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_sphere(&mut topo, 5.0, 16).unwrap();
let area = solid_surface_area(&topo, solid, 0.01).unwrap();
let expected = 4.0 * PI * 25.0;
assert_rel(area, expected, 1e-4, "sphere r=5 surface area");
}
#[test]
fn unit_cube_center_of_mass() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let com = solid_center_of_mass(&topo, solid, 0.1).unwrap();
assert_rel(com.x(), 0.5, 1e-8, "cube CoM x");
assert_rel(com.y(), 0.5, 1e-8, "cube CoM y");
assert_rel(com.z(), 0.5, 1e-8, "cube CoM z");
}
#[test]
fn cylinder_center_of_mass() {
use crate::primitives::make_cylinder;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 3.0, 10.0).unwrap();
let com = solid_center_of_mass(&topo, solid, 0.01).unwrap();
assert_rel(com.x().abs(), 0.0, 1e-4, "cylinder CoM x");
assert_rel(com.y().abs(), 0.0, 1e-4, "cylinder CoM y");
assert_rel(com.z(), 5.0, 1e-4, "cylinder CoM z");
}
#[test]
fn cone_center_of_mass() {
use crate::primitives::make_cone;
let mut topo = Topology::new();
let solid = make_cone(&mut topo, 4.0, 0.0, 12.0).unwrap();
let com = solid_center_of_mass(&topo, solid, 0.01).unwrap();
assert_rel(com.x().abs(), 0.0, 1e-4, "cone CoM x");
assert_rel(com.y().abs(), 0.0, 1e-4, "cone CoM y");
assert_rel(com.z(), 3.0, 1e-4, "cone CoM z = h/4 = 3");
}
#[test]
fn rectangular_box_center_of_mass() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 2.0, 3.0, 4.0).unwrap();
let com = solid_center_of_mass(&topo, solid, 0.1).unwrap();
assert_rel(com.x(), 1.0, 1e-8, "rect box CoM x");
assert_rel(com.y(), 1.5, 1e-8, "rect box CoM y");
assert_rel(com.z(), 2.0, 1e-8, "rect box CoM z");
}
#[test]
fn edge_length_unit_cube() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let tol = Tolerance::new();
let solid_data = topo.solid(solid).unwrap();
let shell = topo.shell(solid_data.outer_shell()).unwrap();
for &fid in shell.faces() {
let face = topo.face(fid).unwrap();
let wire = topo.wire(face.outer_wire()).unwrap();
for oe in wire.edges() {
let len = edge_length(&topo, oe.edge()).unwrap();
assert!(
tol.approx_eq(len, 1.0),
"unit cube edge should have length ~1.0, got {len}"
);
}
}
}
#[test]
fn edge_length_circle() {
use crate::primitives::make_cylinder;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 3.0, 10.0).unwrap();
let solid_data = topo.solid(solid).unwrap();
let shell = topo.shell(solid_data.outer_shell()).unwrap();
let mut found_circle = false;
for &fid in shell.faces() {
let face = topo.face(fid).unwrap();
if matches!(face.surface(), FaceSurface::Plane { .. }) {
let wire = topo.wire(face.outer_wire()).unwrap();
for oe in wire.edges() {
let edge = topo.edge(oe.edge()).unwrap();
if matches!(edge.curve(), brepkit_topology::edge::EdgeCurve::Circle(_)) {
let len = edge_length(&topo, oe.edge()).unwrap();
assert_rel(len, 2.0 * PI * 3.0, 1e-8, "circle edge length");
found_circle = true;
}
}
}
}
assert!(found_circle, "should have found at least one circular edge");
}
#[test]
fn face_perimeter_unit_cube() {
let mut topo = Topology::new();
let solid = make_unit_cube_non_manifold(&mut topo);
let solid_data = topo.solid(solid).unwrap();
let shell = topo.shell(solid_data.outer_shell()).unwrap();
for &fid in shell.faces() {
let perim = face_perimeter(&topo, fid).unwrap();
assert_rel(perim, 4.0, 1e-8, "unit cube face perimeter");
}
}
#[test]
fn wire_length_rectangle() {
use brepkit_topology::builder::make_rectangle_face;
let mut topo = Topology::new();
let fid = make_rectangle_face(&mut topo, 3.0, 5.0, 1e-7).unwrap();
let face = topo.face(fid).unwrap();
let len = wire_length(&topo, face.outer_wire()).unwrap();
assert_rel(len, 16.0, 1e-8, "3x5 rectangle perimeter");
}
#[test]
fn cut_box_cylinder_volume_decreases() {
use crate::boolean::{BooleanOp, boolean};
use crate::primitives::{make_box, make_cylinder};
use crate::transform::transform_solid;
use brepkit_math::mat::Mat4;
use std::f64::consts::PI;
let mut topo = Topology::new();
let bx = make_box(&mut topo, 10.0, 10.0, 10.0).unwrap();
let cyl = make_cylinder(&mut topo, 3.0, 20.0).unwrap();
transform_solid(&mut topo, cyl, &Mat4::translation(5.0, 5.0, 0.0)).unwrap();
let cut = boolean(&mut topo, BooleanOp::Cut, bx, cyl).unwrap();
let box_vol = solid_volume(&topo, bx, 0.01).unwrap();
let cut_vol = solid_volume(&topo, cut, 0.01).unwrap();
let expected = 1000.0 - PI * 9.0 * 10.0;
let s = topo.solid(cut).unwrap();
let sh = topo.shell(s.outer_shell()).unwrap();
assert_eq!(
sh.faces().len(),
7,
"expected 7 faces (6 plane + 1 cylinder bore)"
);
assert!(
cut_vol < box_vol,
"cut volume ({cut_vol:.2}) must be less than box volume ({box_vol:.2})"
);
assert_rel(cut_vol, expected, 0.02, "cut(box, cylinder) volume");
}
#[test]
fn thin_box_volume_and_area() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 10.0, 10.0, 0.001).unwrap();
let vol = solid_volume(&topo, solid, 0.001).unwrap();
let area = solid_surface_area(&topo, solid, 0.001).unwrap();
assert_rel(vol, 0.1, 1e-8, "thin box volume");
assert_rel(area, 200.04, 1e-8, "thin box surface area");
}
#[test]
fn large_box_volume() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 1000.0, 1000.0, 1000.0).unwrap();
let vol = solid_volume(&topo, solid, 1.0).unwrap();
assert_rel(vol, 1e9, 1e-8, "1000^3 box volume");
}
#[test]
fn tiny_box_volume() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 0.001, 0.001, 0.001).unwrap();
let vol = solid_volume(&topo, solid, 0.0001).unwrap();
assert_rel(vol, 1e-9, 1e-8, "0.001^3 box volume");
}
#[test]
fn thin_cylinder_volume() {
use crate::primitives::make_cylinder;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 0.01, 10.0).unwrap();
let vol = solid_volume(&topo, solid, 0.01).unwrap();
let expected = PI * 0.0001 * 10.0;
assert_rel(vol, expected, 1e-8, "thin cylinder r=0.01 h=10 volume");
}
#[test]
fn flat_cylinder_volume() {
use crate::primitives::make_cylinder;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 10.0, 0.01).unwrap();
let vol = solid_volume(&topo, solid, 0.01).unwrap();
let expected = PI * 100.0 * 0.01;
assert_rel(vol, expected, 1e-8, "flat cylinder r=10 h=0.01 volume");
}
#[test]
fn near_pointed_frustum_volume() {
use crate::primitives::make_cone;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cone(&mut topo, 5.0, 0.001, 10.0).unwrap();
let vol = solid_volume(&topo, solid, 0.5).unwrap();
let r1 = 5.0_f64;
let r2 = 0.001_f64;
let expected = PI * 10.0 / 3.0 * (r1 * r1 + r1 * r2 + r2 * r2);
assert_rel(vol, expected, 1e-8, "near-pointed frustum volume");
}
#[test]
fn volume_after_uniform_scale() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 1.0, 1.0, 1.0).unwrap();
let mat = brepkit_math::mat::Mat4::scale(2.0, 2.0, 2.0);
crate::transform::transform_solid(&mut topo, solid, &mat).unwrap();
let vol = solid_volume(&topo, solid, 0.1).unwrap();
assert_rel(vol, 8.0, 1e-8, "unit cube scaled x2 volume");
}
#[test]
fn center_of_mass_after_translation() {
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 1.0, 1.0, 1.0).unwrap();
let mat = brepkit_math::mat::Mat4::translation(10.0, 20.0, 30.0);
crate::transform::transform_solid(&mut topo, solid, &mat).unwrap();
let com = solid_center_of_mass(&topo, solid, 0.1).unwrap();
assert_rel(com.x(), 10.5, 1e-8, "translated CoM x");
assert_rel(com.y(), 20.5, 1e-8, "translated CoM y");
assert_rel(com.z(), 30.5, 1e-8, "translated CoM z");
}
#[test]
fn volume_invariant_under_rotation() {
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = crate::primitives::make_box(&mut topo, 3.0, 4.0, 5.0).unwrap();
let vol_before = solid_volume(&topo, solid, 0.1).unwrap();
let mat = brepkit_math::mat::Mat4::rotation_z(PI / 4.0);
crate::transform::transform_solid(&mut topo, solid, &mat).unwrap();
let vol_after = solid_volume(&topo, solid, 0.1).unwrap();
assert_rel(vol_before, 60.0, 1e-8, "box volume before rotation");
assert_rel(vol_after, 60.0, 1e-8, "box volume after rotation");
}
#[test]
fn cone_face_area_analytic() {
use crate::primitives::make_cone;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cone(&mut topo, 1.0, 0.0, 1.0).unwrap();
let solid_data = topo.solid(solid).unwrap();
let shell = topo.shell(solid_data.outer_shell()).unwrap();
let mut lateral_area = 0.0;
let mut cap_area = 0.0;
for &fid in shell.faces() {
let face = topo.face(fid).unwrap();
let area = face_area(&topo, fid, 0.01).unwrap();
match face.surface() {
FaceSurface::Cone(_) => lateral_area += area,
FaceSurface::Plane { .. } => cap_area += area,
_ => panic!("unexpected surface type in cone"),
}
}
let slant = 2.0_f64.sqrt();
assert_rel(lateral_area, PI * slant, 1e-8, "cone r=1 h=1 lateral area");
assert_rel(cap_area, PI, 2e-4, "cone r=1 h=1 base cap area");
}
#[test]
fn torus_face_area_analytic() {
use crate::primitives::make_torus;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_torus(&mut topo, 2.0, 0.5, 32).unwrap();
let area = solid_surface_area(&topo, solid, 0.01).unwrap();
let expected = 4.0 * PI * PI * 2.0 * 0.5;
assert_rel(area, expected, 1e-8, "torus R=2 r=0.5 surface area");
}
#[test]
fn cylinder_face_area_analytic() {
use crate::primitives::make_cylinder;
use std::f64::consts::PI;
let mut topo = Topology::new();
let solid = make_cylinder(&mut topo, 3.0, 10.0).unwrap();
let solid_data = topo.solid(solid).unwrap();
let shell = topo.shell(solid_data.outer_shell()).unwrap();
let mut lateral_area = 0.0;
let mut cap_area = 0.0;
for &fid in shell.faces() {
let face = topo.face(fid).unwrap();
let area = face_area(&topo, fid, 0.01).unwrap();
match face.surface() {
FaceSurface::Cylinder(_) => lateral_area += area,
FaceSurface::Plane { .. } => cap_area += area,
_ => panic!("unexpected surface type in cylinder"),
}
}
assert_rel(
lateral_area,
2.0 * PI * 3.0 * 10.0,
1e-4,
"cylinder lateral area",
);
assert_rel(cap_area, 2.0 * PI * 9.0, 2e-4, "cylinder cap area");
}
}