use std::collections::HashSet;
use brepkit_math::aabb::Aabb3;
use brepkit_math::vec::Point3;
use brepkit_topology::Topology;
use brepkit_topology::face::{FaceId, FaceSurface};
use brepkit_topology::solid::SolidId;
use super::helpers::collect_solid_vertex_points;
pub fn solid_bounding_box(
topo: &Topology,
solid: SolidId,
) -> Result<Aabb3, crate::OperationsError> {
let points = collect_solid_vertex_points(topo, solid)?;
let mut aabb = Aabb3::try_from_points(points.iter().copied()).ok_or_else(|| {
crate::OperationsError::InvalidInput {
reason: "solid has no vertices".into(),
}
})?;
let solid_data = topo.solid(solid)?;
let shell = topo.shell(solid_data.outer_shell())?;
for &fid in shell.faces() {
if let Ok(face) = topo.face(fid) {
expand_aabb_for_face(topo, &mut aabb, fid, face.surface());
}
}
Ok(aabb)
}
pub fn face_set_bounding_box(
topo: &Topology,
faces: &[FaceId],
) -> Result<Aabb3, crate::OperationsError> {
let mut vertex_ids = HashSet::new();
for &fid in faces {
let face = topo.face(fid)?;
for wire_id in std::iter::once(face.outer_wire()).chain(face.inner_wires().iter().copied())
{
let wire = topo.wire(wire_id)?;
for oe in wire.edges() {
let edge = topo.edge(oe.edge())?;
vertex_ids.insert(edge.start());
vertex_ids.insert(edge.end());
}
}
}
let mut points = Vec::with_capacity(vertex_ids.len());
for vid in vertex_ids {
points.push(topo.vertex(vid)?.point());
}
let mut aabb = Aabb3::try_from_points(points.iter().copied()).ok_or_else(|| {
crate::OperationsError::InvalidInput {
reason: "face set has no vertices".into(),
}
})?;
for &fid in faces {
if let Ok(face) = topo.face(fid) {
expand_aabb_for_face(topo, &mut aabb, fid, face.surface());
}
}
Ok(aabb)
}
fn aabb_include(aabb: &mut Aabb3, p: Point3) {
*aabb = aabb.union(Aabb3 { min: p, max: p });
}
#[allow(clippy::too_many_lines)]
fn expand_aabb_for_face(
topo: &Topology,
aabb: &mut Aabb3,
face_id: brepkit_topology::face::FaceId,
surface: &FaceSurface,
) {
sample_face_wire_midpoints(topo, aabb, face_id);
match surface {
FaceSurface::Plane { .. } => {}
FaceSurface::Sphere(s) => {
let c = s.center();
let r = s.radius();
aabb_include(aabb, Point3::new(c.x() - r, c.y() - r, c.z() - r));
aabb_include(aabb, Point3::new(c.x() + r, c.y() + r, c.z() + r));
}
FaceSurface::Torus(t) => {
let c = t.center();
let r_major = t.major_radius();
let r_minor = t.minor_radius();
let axis = t.z_axis();
let hx = r_major * (1.0 - axis.x() * axis.x()).max(0.0).sqrt() + r_minor;
let hy = r_major * (1.0 - axis.y() * axis.y()).max(0.0).sqrt() + r_minor;
let hz = r_major * (1.0 - axis.z() * axis.z()).max(0.0).sqrt() + r_minor;
aabb_include(aabb, Point3::new(c.x() - hx, c.y() - hy, c.z() - hz));
aabb_include(aabb, Point3::new(c.x() + hx, c.y() + hy, c.z() + hz));
}
FaceSurface::Cylinder(c) => {
expand_cylinder_at_vertices(topo, aabb, face_id, c);
}
FaceSurface::Cone(c) => {
expand_cone_at_vertices(topo, aabb, face_id, c);
}
FaceSurface::Nurbs(nurbs) => {
let (u_min, u_max) = nurbs.domain_u();
let (v_min, v_max) = nurbs.domain_v();
let n_samples = 4;
#[allow(clippy::cast_precision_loss)]
for iu in 1..n_samples {
let u = u_min + (u_max - u_min) * (iu as f64) / (n_samples as f64);
for iv in 1..n_samples {
let v = v_min + (v_max - v_min) * (iv as f64) / (n_samples as f64);
aabb_include(aabb, nurbs.evaluate(u, v));
}
}
}
}
}
fn sample_face_wire_midpoints(
topo: &Topology,
aabb: &mut Aabb3,
face_id: brepkit_topology::face::FaceId,
) -> bool {
let Ok(face) = topo.face(face_id) else {
return false;
};
let Ok(wire) = topo.wire(face.outer_wire()) else {
return false;
};
let mut has_curved = false;
for oe in wire.edges() {
let Ok(edge) = topo.edge(oe.edge()) else {
continue;
};
if !matches!(edge.curve(), brepkit_topology::edge::EdgeCurve::Line) {
has_curved = true;
}
let Ok(sv) = topo.vertex(edge.start()) else {
continue;
};
let Ok(ev) = topo.vertex(edge.end()) else {
continue;
};
let p_start = sv.point();
let p_end = ev.point();
let (t0, t1) = edge.curve().domain_with_endpoints(p_start, p_end);
for &frac in &[0.25, 0.5, 0.75] {
let t = t0 + (t1 - t0) * frac;
let pt = edge.curve().evaluate_with_endpoints(t, p_start, p_end);
aabb_include(aabb, pt);
}
}
has_curved
}
fn expand_cylinder_at_vertices(
topo: &Topology,
aabb: &mut Aabb3,
face_id: brepkit_topology::face::FaceId,
cyl: &brepkit_math::surfaces::CylindricalSurface,
) {
let Ok(face) = topo.face(face_id) else {
return;
};
let Ok(wire) = topo.wire(face.outer_wire()) else {
return;
};
let axis = cyl.axis();
let origin = cyl.origin();
let r = cyl.radius();
let rx = r * (1.0 - axis.x() * axis.x()).max(0.0).sqrt();
let ry = r * (1.0 - axis.y() * axis.y()).max(0.0).sqrt();
let rz = r * (1.0 - axis.z() * axis.z()).max(0.0).sqrt();
for oe in wire.edges() {
let Ok(edge) = topo.edge(oe.edge()) else {
continue;
};
for vid in [edge.start(), edge.end()] {
let Ok(v) = topo.vertex(vid) else {
continue;
};
let rel = brepkit_math::vec::Vec3::new(
v.point().x() - origin.x(),
v.point().y() - origin.y(),
v.point().z() - origin.z(),
);
let t = axis.dot(rel);
let coa = Point3::new(
origin.x() + axis.x() * t,
origin.y() + axis.y() * t,
origin.z() + axis.z() * t,
);
aabb_include(aabb, Point3::new(coa.x() - rx, coa.y() - ry, coa.z() - rz));
aabb_include(aabb, Point3::new(coa.x() + rx, coa.y() + ry, coa.z() + rz));
}
}
}
fn expand_cone_at_vertices(
topo: &Topology,
aabb: &mut Aabb3,
face_id: brepkit_topology::face::FaceId,
cone: &brepkit_math::surfaces::ConicalSurface,
) {
use brepkit_math::vec::Vec3;
let Ok(face) = topo.face(face_id) else {
return;
};
let Ok(wire) = topo.wire(face.outer_wire()) else {
return;
};
let axis = cone.axis();
let apex = cone.apex();
let sx = (1.0 - axis.x() * axis.x()).max(0.0).sqrt();
let sy = (1.0 - axis.y() * axis.y()).max(0.0).sqrt();
let sz = (1.0 - axis.z() * axis.z()).max(0.0).sqrt();
for oe in wire.edges() {
let Ok(edge) = topo.edge(oe.edge()) else {
continue;
};
for vid in [edge.start(), edge.end()] {
let Ok(v) = topo.vertex(vid) else {
continue;
};
let rel = Vec3::new(
v.point().x() - apex.x(),
v.point().y() - apex.y(),
v.point().z() - apex.z(),
);
let t = axis.dot(rel);
let coa = Point3::new(
apex.x() + axis.x() * t,
apex.y() + axis.y() * t,
apex.z() + axis.z() * t,
);
let perp = Vec3::new(
rel.x() - axis.x() * t,
rel.y() - axis.y() * t,
rel.z() - axis.z() * t,
);
let r = perp.length();
aabb_include(
aabb,
Point3::new(coa.x() - r * sx, coa.y() - r * sy, coa.z() - r * sz),
);
aabb_include(
aabb,
Point3::new(coa.x() + r * sx, coa.y() + r * sy, coa.z() + r * sz),
);
}
}
}