Skip to main content

brep_kernel/construction/
polygon_solids.rs

1//! Solids bounded by plane polygons: any closed set of them, and the pyramid
2//! on a polygon.
3use crate::{face_from_surface_trimmed, make_line, make_plane, solid_from_sheets, BrepSolid, KernelRefusal, KernelStage, OrRefuse, Vec3};
4
5fn input(what: &'static str, message: String) -> KernelRefusal {
6    KernelRefusal::input(KernelStage::Collect, what, message)
7}
8
9/// The sheet of one flat face inside the polygon `corners` (in order, either
10/// way round), which must lie in one plane.
11fn polygon_face(corners: &[Vec3], index: usize) -> Result<BrepSolid, KernelRefusal> {
12    if corners.len() < 3 {
13        return Err(input("polygon_corners", format!("solidFromPolygons: polygon {index} has fewer than three corners")));
14    }
15    let origin = corners[0];
16    let arms: Vec<Vec3> = corners.iter().map(|corner| corner.sub(origin)).collect();
17    let size = arms.iter().map(|arm| arm.length()).fold(0.0, f64::max);
18    let flat = || input("polygon_plane", format!("solidFromPolygons: polygon {index} does not span a plane"));
19    // the plane's frame: along the longest arm, and square to it toward the arm farthest off it
20    let longest = arms.iter().copied().fold(Vec3::new(0.0, 0.0, 0.0), |best, arm| if arm.length() > best.length() { arm } else { best });
21    let e1 = longest.normalized().map_err(|_| flat())?;
22    let off = |arm: Vec3| arm.sub(e1.scale(arm.dot(e1)));
23    let widest = arms.iter().map(|arm| off(*arm)).fold(Vec3::new(0.0, 0.0, 0.0), |best, arm| if arm.length() > best.length() { arm } else { best });
24    if widest.length() <= 1e-9 * size {
25        return Err(flat());
26    }
27    let e2 = widest.normalized().map_err(|_| flat())?;
28    let normal = e1.cross(e2);
29    if arms.iter().any(|arm| arm.dot(normal).abs() > 1e-9 * size) {
30        return Err(input("polygon_plane", format!("solidFromPolygons: polygon {index}'s corners do not lie in one plane")));
31    }
32    let reach = |axis: Vec3| arms.iter().map(|arm| arm.dot(axis)).fold((0.0f64, 0.0f64), |(low, high), at| (low.min(at), high.max(at)));
33    let ((low1, high1), (low2, high2)) = (reach(e1), reach(e2));
34    let corner = origin.add(e1.scale(low1 - 0.5 * size)).add(e2.scale(low2 - 0.5 * size));
35    let plane = make_plane(corner, e1, e2, high1 - low1 + size, high2 - low2 + size).or_refuse(KernelStage::Collect, "solidFromPolygons.plane")?;
36    let sides = (0..corners.len())
37        .map(|k| make_line(corners[k], corners[(k + 1) % corners.len()]))
38        .collect::<Result<Vec<_>, _>>()
39        .or_refuse(KernelStage::Collect, "solidFromPolygons.side")?;
40    face_from_surface_trimmed(&plane, &[sides])
41}
42
43/// The solid bounded by the plane polygons `faces`, each its corners in order
44/// (either way round): every polygon a flat face, joined along their common
45/// sides ([`crate::solid_from_sheets`]) and turned outward. The polygons need
46/// not be convex, nor the solid; each side of each polygon must be a whole
47/// side of exactly one other.
48pub fn solid_from_polygons(faces: &[Vec<Vec3>]) -> Result<BrepSolid, KernelRefusal> {
49    if faces.len() < 4 {
50        return Err(input("polygon_count", "solidFromPolygons: a solid has at least four faces".into()));
51    }
52    let sheets = faces.iter().enumerate().map(|(index, corners)| polygon_face(corners, index)).collect::<Result<Vec<_>, _>>()?;
53    let size = faces.iter().flatten().map(|corner| corner.sub(faces[0][0]).length()).fold(0.0, f64::max);
54    solid_from_sheets(&sheets, (1e-8 * size).max(1e-7))
55}
56
57/// The pyramid on the plane polygon `base` (its corners in order, either way
58/// round) with its point at `apex`: the base and one triangle on each of its
59/// sides. The base need not be regular, nor the apex over its middle.
60pub fn make_pyramid_on_polygon(base: &[Vec3], apex: Vec3) -> Result<BrepSolid, KernelRefusal> {
61    if base.len() < 3 {
62        return Err(input("pyramid_base", "makePyramidOnPolygon: the base has fewer than three corners".into()));
63    }
64    let mut faces = vec![base.to_vec()];
65    faces.extend((0..base.len()).map(|k| vec![base[k], base[(k + 1) % base.len()], apex]));
66    solid_from_polygons(&faces)
67}
68