diff --git a/crates/fj-core/src/algorithms/bounding_volume/cycle.rs b/crates/fj-core/src/algorithms/bounding_volume/cycle.rs new file mode 100644 index 000000000..c598668f8 --- /dev/null +++ b/crates/fj-core/src/algorithms/bounding_volume/cycle.rs @@ -0,0 +1,18 @@ +use fj_math::Aabb; + +use crate::objects::Cycle; + +impl super::BoundingVolume<2> for Cycle { + fn aabb(&self) -> Option> { + let mut aabb: Option> = None; + + for half_edge in self.half_edges() { + let new_aabb = half_edge + .aabb() + .expect("`HalfEdge` can always compute AABB"); + aabb = Some(aabb.map_or(new_aabb, |aabb| aabb.merged(&new_aabb))); + } + + aabb + } +} diff --git a/crates/fj-core/src/algorithms/bounding_volume/edge.rs b/crates/fj-core/src/algorithms/bounding_volume/edge.rs new file mode 100644 index 000000000..54d044ff8 --- /dev/null +++ b/crates/fj-core/src/algorithms/bounding_volume/edge.rs @@ -0,0 +1,13 @@ +use fj_math::Aabb; + +use crate::objects::HalfEdge; + +impl super::BoundingVolume<2> for HalfEdge { + fn aabb(&self) -> Option> { + let points = self.boundary().map(|point_curve| { + self.curve().point_from_path_coords(point_curve) + }); + + Some(Aabb::<2>::from_points(points)) + } +} diff --git a/crates/fj-core/src/algorithms/bounding_volume/face.rs b/crates/fj-core/src/algorithms/bounding_volume/face.rs new file mode 100644 index 000000000..8926680ac --- /dev/null +++ b/crates/fj-core/src/algorithms/bounding_volume/face.rs @@ -0,0 +1,24 @@ +use fj_math::Aabb; + +use crate::{geometry::curve::GlobalPath, objects::Face}; + +impl super::BoundingVolume<3> for Face { + fn aabb(&self) -> Option> { + self.exterior().aabb().map(|aabb2| { + let surface = self.surface().geometry(); + + match surface.u { + GlobalPath::Circle(_) => { + // I don't currently have an example model to test this + // with. This should change soon, and then this will panic + // and can be addressed. + todo!("Computing AABB of curved face is not supported yet") + } + GlobalPath::Line(_) => Aabb { + min: surface.point_from_surface_coords(aabb2.min), + max: surface.point_from_surface_coords(aabb2.max), + }, + } + }) + } +} diff --git a/crates/fj-core/src/algorithms/bounding_volume/mod.rs b/crates/fj-core/src/algorithms/bounding_volume/mod.rs new file mode 100644 index 000000000..34372592c --- /dev/null +++ b/crates/fj-core/src/algorithms/bounding_volume/mod.rs @@ -0,0 +1,17 @@ +//! Compute a bounding volume for an object + +mod cycle; +mod edge; +mod face; +mod shell; +mod solid; + +use fj_math::Aabb; + +/// Compute a bounding volume for an object +pub trait BoundingVolume { + /// Compute an axis-aligned bounding box (AABB) + /// + /// Return `None`, if no AABB can be computed (if the object is empty). + fn aabb(&self) -> Option>; +} diff --git a/crates/fj-core/src/algorithms/bounding_volume/shell.rs b/crates/fj-core/src/algorithms/bounding_volume/shell.rs new file mode 100644 index 000000000..0d889ffae --- /dev/null +++ b/crates/fj-core/src/algorithms/bounding_volume/shell.rs @@ -0,0 +1,19 @@ +use fj_math::Aabb; + +use crate::objects::Shell; + +impl super::BoundingVolume<3> for Shell { + fn aabb(&self) -> Option> { + let mut aabb: Option> = None; + + for face in self.faces() { + let new_aabb = face.aabb(); + aabb = aabb.map_or(new_aabb, |aabb| match new_aabb { + Some(new_aabb) => Some(aabb.merged(&new_aabb)), + None => Some(aabb), + }); + } + + aabb + } +} diff --git a/crates/fj-core/src/algorithms/bounding_volume/solid.rs b/crates/fj-core/src/algorithms/bounding_volume/solid.rs new file mode 100644 index 000000000..4c6b5a006 --- /dev/null +++ b/crates/fj-core/src/algorithms/bounding_volume/solid.rs @@ -0,0 +1,19 @@ +use fj_math::Aabb; + +use crate::objects::Solid; + +impl super::BoundingVolume<3> for Solid { + fn aabb(&self) -> Option> { + let mut aabb: Option> = None; + + for shell in self.shells() { + let new_aabb = shell.aabb(); + aabb = aabb.map_or(new_aabb, |aabb| match new_aabb { + Some(new_aabb) => Some(aabb.merged(&new_aabb)), + None => Some(aabb), + }); + } + + aabb + } +} diff --git a/crates/fj-core/src/algorithms/mod.rs b/crates/fj-core/src/algorithms/mod.rs index 1e94f868b..2786c3f95 100644 --- a/crates/fj-core/src/algorithms/mod.rs +++ b/crates/fj-core/src/algorithms/mod.rs @@ -4,6 +4,7 @@ //! on their respective purpose. pub mod approx; +pub mod bounding_volume; pub mod intersect; pub mod reverse; pub mod sweep; diff --git a/crates/fj/src/handle_model.rs b/crates/fj/src/handle_model.rs index be9ccbbbc..66818a985 100644 --- a/crates/fj/src/handle_model.rs +++ b/crates/fj/src/handle_model.rs @@ -1,8 +1,11 @@ use std::ops::Deref; -use fj_core::algorithms::{approx::Tolerance, triangulate::Triangulate}; +use fj_core::algorithms::{ + approx::Tolerance, bounding_volume::BoundingVolume, + triangulate::Triangulate, +}; use fj_interop::model::Model; -use fj_math::Aabb; +use fj_math::{Aabb, Point}; use crate::Args; @@ -20,7 +23,12 @@ pub fn handle_model( ) -> Result where for<'r> (&'r M, Tolerance): Triangulate, + M: BoundingVolume<3>, { + let aabb = model.aabb().unwrap_or(Aabb { + min: Point::origin(), + max: Point::origin(), + }); let mesh = (model.deref(), tolerance.into()).triangulate(); let args = Args::parse(); @@ -29,7 +37,6 @@ where return Ok(()); } - let aabb = Aabb::<3>::from_points(mesh.vertices()); let model = Model { mesh, aabb }; crate::window::display(model, false)?;