diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs index 92defc4f0..be930aabc 100644 --- a/src/kernel/mod.rs +++ b/src/kernel/mod.rs @@ -3,84 +3,3 @@ pub mod geometry; pub mod shapes; pub mod topology; pub mod triangulation; - -use crate::{ - debug::DebugInfo, - math::{Aabb, Scalar}, -}; - -use self::topology::{edges::Edges, faces::Faces, vertices::Vertices}; - -/// Implemented by all shapes -pub trait Shape { - /// Access the axis-aligned bounding box of a shape - /// - /// If a shape is empty, its [`Aabb`]'s `min` and `max` points must be equal - /// (but are otherwise not specified). - fn bounding_volume(&self) -> Aabb<3>; - - /// Compute triangles to approximate the shape's faces - /// - /// The shape defined by the approximated triangles must be fully contained - /// within the actual shape. - /// - /// `tolerance` defines by how far this triangulation is allowed to deviate - /// from the faces' actual dimensions. - fn faces(&self, tolerance: Scalar, debug: &mut DebugInfo) -> Faces; - - /// Access the edges of the shape - fn edges(&self) -> Edges; - - /// Return the shape's vertices - fn vertices(&self) -> Vertices; -} - -macro_rules! dispatch { - ($($method:ident($($arg_name:ident: $arg_ty:ty,)*) -> $ret:ty;)*) => { - impl Shape for fj::Shape { - $( - fn $method(&self, $($arg_name: $arg_ty,)*) -> $ret { - match self { - Self::Shape2d(shape) => shape.$method($($arg_name,)*), - Self::Shape3d(shape) => shape.$method($($arg_name,)*), - } - } - )* - } - - impl Shape for fj::Shape2d { - $( - fn $method(&self, $($arg_name: $arg_ty,)*) -> $ret { - match self { - Self::Circle(shape) => shape.$method($($arg_name,)*), - Self::Difference(shape) => shape.$method($($arg_name,)*), - Self::Sketch(shape) => shape.$method($($arg_name,)*), - } - } - )* - } - - impl Shape for fj::Shape3d { - $( - fn $method(&self, $($arg_name: $arg_ty,)*) -> $ret { - match self { - Self::Difference(shape) => shape.$method($($arg_name,)*), - Self::Sweep(shape) => shape.$method($($arg_name,)*), - Self::Transform(shape) => shape.$method($($arg_name,)*), - Self::Union(shape) => shape.$method($($arg_name,)*), - } - } - )* - } - }; -} - -dispatch! { - bounding_volume() -> Aabb<3>; - faces( - tolerance: Scalar, - debug: &mut DebugInfo, - ) -> Faces; - edges() -> Edges; - vertices() -> Vertices; -} diff --git a/src/kernel/shapes/circle.rs b/src/kernel/shapes/circle.rs index a842d8797..959328488 100644 --- a/src/kernel/shapes/circle.rs +++ b/src/kernel/shapes/circle.rs @@ -6,34 +6,37 @@ use crate::{ edges::{Edge, Edges}, faces::{Face, Faces}, vertices::Vertices, + Shape, }, - Shape, }, math::{Aabb, Point, Scalar}, }; -impl Shape for fj::Circle { - fn bounding_volume(&self) -> Aabb<3> { - Aabb { - min: Point::from([-self.radius, -self.radius, 0.0]), - max: Point::from([self.radius, self.radius, 0.0]), - } - } +use super::ToShape; + +impl ToShape for fj::Circle { + fn to_shape(&self, _: Scalar, _: &mut DebugInfo) -> Shape { + // Circles have just a single round edge with no vertices. + let vertices = Vertices(Vec::new()); - fn faces(&self, _: Scalar, _: &mut DebugInfo) -> Faces { let edges = Edges::single_cycle([Edge::circle(self.radius)]); - Faces(vec![Face::Face { - edges, + + let faces = Faces(vec![Face::Face { + edges: edges.clone(), surface: Surface::x_y_plane(), - }]) - } + }]); - fn edges(&self) -> Edges { - Edges::single_cycle([Edge::circle(self.radius)]) + Shape { + vertices, + edges, + faces, + } } - fn vertices(&self) -> Vertices { - // Circles have just a single round edge with no vertices. - Vertices(Vec::new()) + fn bounding_volume(&self) -> Aabb<3> { + Aabb { + min: Point::from([-self.radius, -self.radius, 0.0]), + max: Point::from([self.radius, self.radius, 0.0]), + } } } diff --git a/src/kernel/shapes/difference_2d.rs b/src/kernel/shapes/difference_2d.rs index 2b8db4dcf..c5e95f8cf 100644 --- a/src/kernel/shapes/difference_2d.rs +++ b/src/kernel/shapes/difference_2d.rs @@ -1,95 +1,95 @@ use crate::{ debug::DebugInfo, - kernel::{ - topology::{ - edges::Edges, - faces::{Face, Faces}, - vertices::Vertices, - }, + kernel::topology::{ + edges::Edges, + faces::{Face, Faces}, + vertices::Vertices, Shape, }, math::{Aabb, Scalar}, }; -impl Shape for fj::Difference2d { - fn bounding_volume(&self) -> Aabb<3> { - // This is a conservative estimate of the bounding box: It's never going - // to be bigger than the bounding box of the original shape that another - // is being subtracted from. - self.a.bounding_volume() - } +use super::ToShape; - fn faces(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Faces { +impl ToShape for fj::Difference2d { + fn to_shape(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Shape { // This method assumes that `b` is fully contained within `a`: // https://github.com/hannobraun/Fornjot/issues/92 - let mut a = self.a.faces(tolerance, debug_info); - let mut b = self.b.faces(tolerance, debug_info); + let mut a = self.a.to_shape(tolerance, debug_info); + let mut b = self.b.to_shape(tolerance, debug_info); - let (a, b) = if a.0.len() == 1 && b.0.len() == 1 { - // Can't panic. We just checked that length of `a` and `b` is 1. - (a.0.pop().unwrap(), b.0.pop().unwrap()) - } else { - // See issue: - // https://github.com/hannobraun/Fornjot/issues/95 - todo!( - "The 2-dimensional difference operation only supports one face \ - in each operand." - ); - }; + let edges = { + let (a, b) = if a.edges.cycles.len() == 1 + && b.edges.cycles.len() == 1 + { + (a.edges.cycles.pop().unwrap(), b.edges.cycles.pop().unwrap()) + } else { + // See issue: + // https://github.com/hannobraun/Fornjot/issues/95 + todo!( + "The 2-dimensional difference operation only supports one \ + cycle in each operand." + ); + }; - let (a, b, surface_a, surface_b) = match (a, b) { - ( - Face::Face { - edges: a, - surface: surface_a, - }, - Face::Face { - edges: b, - surface: surface_b, - }, - ) => (a, b, surface_a, surface_b), - _ => { - // None of the 2D types still use the triangles representation. - unreachable!() - } + Edges { cycles: vec![a, b] } }; - if surface_a != surface_b { - // Panicking is not great, but as long as we don't have a real error - // handling mechanism, it will do. - panic!("Trying to subtract sketches with different surfaces.") - } - let surface = surface_a; - - let mut edges = a; - edges.cycles.extend(b.cycles); + let faces = { + let (a, b) = if a.faces.0.len() == 1 && b.faces.0.len() == 1 { + // Can't panic. We just checked that length of `a` and `b` is 1. + (a.faces.0.pop().unwrap(), b.faces.0.pop().unwrap()) + } else { + // See issue: + // https://github.com/hannobraun/Fornjot/issues/95 + todo!( + "The 2-dimensional difference operation only supports one \ + face in each operand." + ); + }; - Faces(vec![Face::Face { edges, surface }]) - } + let (a, b, surface_a, surface_b) = match (a, b) { + ( + Face::Face { + edges: a, + surface: surface_a, + }, + Face::Face { + edges: b, + surface: surface_b, + }, + ) => (a, b, surface_a, surface_b), + _ => { + // None of the 2D types still use the triangles representation. + unreachable!() + } + }; - fn edges(&self) -> Edges { - // This method assumes that `b` is fully contained within `a`: - // https://github.com/hannobraun/Fornjot/issues/92 + if surface_a != surface_b { + // Panicking is not great, but as long as we don't have a real + // error handling mechanism, it will do. + panic!("Trying to subtract sketches with different surfaces.") + } + let surface = surface_a; - let mut a = self.a.edges(); - let mut b = self.b.edges(); + let mut edges = a; + edges.cycles.extend(b.cycles); - let (a, b) = if a.cycles.len() == 1 && b.cycles.len() == 1 { - (a.cycles.pop().unwrap(), b.cycles.pop().unwrap()) - } else { - // See issue: - // https://github.com/hannobraun/Fornjot/issues/95 - todo!( - "The 2-dimensional difference operation only supports one \ - cycle in each operand." - ); + Faces(vec![Face::Face { edges, surface }]) }; - Edges { cycles: vec![a, b] } + Shape { + vertices: Vertices(Vec::new()), + edges, + faces, + } } - fn vertices(&self) -> Vertices { - todo!() + fn bounding_volume(&self) -> Aabb<3> { + // This is a conservative estimate of the bounding box: It's never going + // to be bigger than the bounding box of the original shape that another + // is being subtracted from. + self.a.bounding_volume() } } diff --git a/src/kernel/shapes/difference_3d.rs b/src/kernel/shapes/difference_3d.rs index 354ed8542..92496f37c 100644 --- a/src/kernel/shapes/difference_3d.rs +++ b/src/kernel/shapes/difference_3d.rs @@ -1,29 +1,24 @@ use crate::{ debug::DebugInfo, - kernel::{ - topology::{edges::Edges, faces::Faces, vertices::Vertices}, - Shape, - }, + kernel::topology::{edges::Edges, faces::Faces, vertices::Vertices, Shape}, math::{Aabb, Scalar}, }; -impl Shape for fj::Difference { +use super::ToShape; + +impl ToShape for fj::Difference { + fn to_shape(&self, _: Scalar, _: &mut DebugInfo) -> Shape { + Shape { + vertices: Vertices(Vec::new()), + edges: Edges { cycles: Vec::new() }, + faces: Faces(Vec::new()), + } + } + fn bounding_volume(&self) -> Aabb<3> { // This is a conservative estimate of the bounding box: It's never going // to be bigger than the bounding box of the original shape that another // is being subtracted from. self.a.bounding_volume() } - - fn faces(&self, _tolerance: Scalar, _: &mut DebugInfo) -> Faces { - todo!() - } - - fn edges(&self) -> Edges { - todo!() - } - - fn vertices(&self) -> Vertices { - todo!() - } } diff --git a/src/kernel/shapes/mod.rs b/src/kernel/shapes/mod.rs index 4053782c4..67c1e8b02 100644 --- a/src/kernel/shapes/mod.rs +++ b/src/kernel/shapes/mod.rs @@ -5,3 +5,70 @@ pub mod sketch; pub mod sweep; pub mod transform; pub mod union; + +use crate::{ + debug::DebugInfo, + math::{Aabb, Scalar}, +}; + +use super::topology::Shape; + +/// Implemented by all shapes +pub trait ToShape { + /// Compute the boundary representation of the shape + fn to_shape(&self, tolerance: Scalar, debug: &mut DebugInfo) -> Shape; + + /// Access the axis-aligned bounding box of a shape + /// + /// If a shape is empty, its [`Aabb`]'s `min` and `max` points must be equal + /// (but are otherwise not specified). + fn bounding_volume(&self) -> Aabb<3>; +} + +macro_rules! dispatch { + ($($method:ident($($arg_name:ident: $arg_ty:ty,)*) -> $ret:ty;)*) => { + impl ToShape for fj::Shape { + $( + fn $method(&self, $($arg_name: $arg_ty,)*) -> $ret { + match self { + Self::Shape2d(shape) => shape.$method($($arg_name,)*), + Self::Shape3d(shape) => shape.$method($($arg_name,)*), + } + } + )* + } + + impl ToShape for fj::Shape2d { + $( + fn $method(&self, $($arg_name: $arg_ty,)*) -> $ret { + match self { + Self::Circle(shape) => shape.$method($($arg_name,)*), + Self::Difference(shape) => shape.$method($($arg_name,)*), + Self::Sketch(shape) => shape.$method($($arg_name,)*), + } + } + )* + } + + impl ToShape for fj::Shape3d { + $( + fn $method(&self, $($arg_name: $arg_ty,)*) -> $ret { + match self { + Self::Difference(shape) => shape.$method($($arg_name,)*), + Self::Sweep(shape) => shape.$method($($arg_name,)*), + Self::Transform(shape) => shape.$method($($arg_name,)*), + Self::Union(shape) => shape.$method($($arg_name,)*), + } + } + )* + } + }; +} + +dispatch! { + to_shape( + tolerance: Scalar, + debug: &mut DebugInfo, + ) -> Shape; + bounding_volume() -> Aabb<3>; +} diff --git a/src/kernel/shapes/sketch.rs b/src/kernel/shapes/sketch.rs index 488cad6ae..d72bdcd0c 100644 --- a/src/kernel/shapes/sketch.rs +++ b/src/kernel/shapes/sketch.rs @@ -6,87 +6,77 @@ use crate::{ edges::{Edge, Edges}, faces::{Face, Faces}, vertices::{Vertex, Vertices}, + Shape, }, - Shape, }, math::{Aabb, Point, Scalar, Vector}, }; -impl Shape for fj::Sketch { - fn bounding_volume(&self) -> Aabb<3> { - let vertices = self.vertices(); - Aabb::<3>::from_points( - vertices.0.iter().map(|vertex| *vertex.location()), - ) - } +use super::ToShape; - fn faces(&self, _: Scalar, _: &mut DebugInfo) -> Faces { - let edges = self.edges(); - let face = Face::Face { - edges, - surface: Surface::x_y_plane(), - }; - Faces(vec![face]) - } +impl ToShape for fj::Sketch { + fn to_shape(&self, _: Scalar, _: &mut DebugInfo) -> Shape { + let vertices = self + .to_points() + .into_iter() + .map(|[x, y]| Vertex::create_at(Point::from([x, y, 0.]))) + .collect(); + let vertices = Vertices(vertices); - fn edges(&self) -> Edges { - let vertices = match self.vertices() { - vertices if vertices.0.is_empty() => vertices.0, - vertices => { - let mut vertices = vertices.0; + let edges = { + let vertices = match vertices.clone() { + vertices if vertices.0.is_empty() => vertices.0, + vertices => { + let mut vertices = vertices.0; - // Add the first vertex at the end again, to close the loop. + // Add the first vertex at the end again, to close the loop. + // + // This can't panic. This `match` expression makes sure that + // there are vertices. + vertices.push(vertices[0]); + vertices + } + }; + + let mut edges = Vec::new(); + for window in vertices.windows(2) { + // Can't panic, we passed `2` to `windows`. // - // This can't panic. This `match` expression makes sure that - // there are vertices. - vertices.push(vertices[0]); - vertices + // Can be cleaned up, once `array_windows` is stable. + let a = window[0]; + let b = window[1]; + + let line = Curve::Line(Line { + origin: *a.location(), + direction: Vector::from(*b.location() - *a.location()), + }); + let edge = Edge::new(line, Some([a, b])); + + edges.push(edge); } - }; - let mut edges = Vec::new(); - for window in vertices.windows(2) { - // Can't panic, we passed `2` to `windows`. - // - // Can be cleaned up, once `array_windows` is stable. - let a = window[0]; - let b = window[1]; + Edges::single_cycle(edges) + }; - let line = Curve::Line(Line { - origin: *a.location(), - direction: Vector::from(*b.location() - *a.location()), - }); - let edge = Edge::new(line, Some([a, b])); + let face = Face::Face { + edges: edges.clone(), + surface: Surface::x_y_plane(), + }; + let faces = Faces(vec![face]); - edges.push(edge); + Shape { + vertices, + edges, + faces, } - - Edges::single_cycle(edges) } - fn vertices(&self) -> Vertices { - let vertices = self - .to_points() - .into_iter() - // These calls to `Vertex::create_at` don't follow the rules that - // the documentation of `create_at` lays out. This method can be - // called by an outside caller, and is additionally called by other - // methods in this trait implementation, either directly or - // indirectly. - // - // This means the same vertices are re-created multiple times, which - // is forbidden. I don't think this is causing any actual problems, - // since these `Vertex` instances are created from points that come - // directly from the user, and aren't being computed here. - // - // But still, this rule exists for a reason: to prevent subtle bugs - // from creeping in. We should follow it, here and everywhere. - // - // Please refer to this issue for more context on the problem, as - // well as a proposed solution: - // https://github.com/hannobraun/Fornjot/issues/176 - .map(|[x, y]| Vertex::create_at(Point::from([x, y, 0.]))) - .collect(); - Vertices(vertices) + fn bounding_volume(&self) -> Aabb<3> { + Aabb::<3>::from_points( + self.to_points() + .into_iter() + .map(Point::from) + .map(Point::to_xyz), + ) } } diff --git a/src/kernel/shapes/sweep.rs b/src/kernel/shapes/sweep.rs index 2ef391fd9..27037cd71 100644 --- a/src/kernel/shapes/sweep.rs +++ b/src/kernel/shapes/sweep.rs @@ -11,20 +11,18 @@ use crate::{ edges::Edges, faces::{Face, Faces}, vertices::Vertices, + Shape, }, - Shape, }, math::{Aabb, Scalar, Transform}, }; -impl Shape for fj::Sweep { - fn bounding_volume(&self) -> Aabb<3> { - let mut aabb = self.shape.bounding_volume(); - aabb.max.z = self.length.into(); - aabb - } +use super::ToShape; + +impl ToShape for fj::Sweep { + fn to_shape(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Shape { + let original_shape = self.shape.to_shape(tolerance, debug_info); - fn faces(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Faces { let rotation = Isometry::rotation(vector![PI, 0., 0.]).into(); let translation = Isometry::translation(0.0, 0.0, self.length).into(); @@ -32,8 +30,7 @@ impl Shape for fj::Sweep { let mut top_faces = Vec::new(); let mut side_faces = Vec::new(); - let original_faces = self.shape.faces(tolerance, debug_info); - for face in original_faces.0 { + for face in original_shape.faces.0 { // This only works for faces that are symmetric to the x-axis. // // See issue: @@ -43,7 +40,7 @@ impl Shape for fj::Sweep { top_faces.push(face.transform(&translation)); } - for cycle in self.shape.edges().cycles { + for cycle in original_shape.edges.cycles { let approx = Approximation::for_cycle(&cycle, tolerance); // This will only work correctly, if the cycle consists of one edge. @@ -76,14 +73,18 @@ impl Shape for fj::Sweep { faces.extend(top_faces); faces.extend(side_faces); - Faces(faces) - } + let faces = Faces(faces); - fn edges(&self) -> Edges { - todo!() + Shape { + vertices: Vertices(Vec::new()), + edges: Edges { cycles: Vec::new() }, + faces, + } } - fn vertices(&self) -> Vertices { - todo!() + fn bounding_volume(&self) -> Aabb<3> { + let mut aabb = self.shape.bounding_volume(); + aabb.max.z = self.length.into(); + aabb } } diff --git a/src/kernel/shapes/transform.rs b/src/kernel/shapes/transform.rs index e9cd65d78..e93eebfee 100644 --- a/src/kernel/shapes/transform.rs +++ b/src/kernel/shapes/transform.rs @@ -2,30 +2,29 @@ use parry3d_f64::math::Isometry; use crate::{ debug::DebugInfo, - kernel::{ - topology::{edges::Edges, faces::Faces, vertices::Vertices}, - Shape, - }, + kernel::topology::{edges::Edges, vertices::Vertices, Shape}, math::{Aabb, Scalar, Transform}, }; -impl Shape for fj::Transform { - fn bounding_volume(&self) -> Aabb<3> { - transform(self).transform_aabb(&self.shape.bounding_volume()) - } +use super::ToShape; - fn faces(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Faces { - self.shape - .faces(tolerance, debug_info) - .transform(&transform(self)) - } +impl ToShape for fj::Transform { + fn to_shape(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Shape { + let faces = self + .shape + .to_shape(tolerance, debug_info) + .faces + .transform(&transform(self)); - fn edges(&self) -> Edges { - todo!() + Shape { + vertices: Vertices(Vec::new()), + edges: Edges { cycles: Vec::new() }, + faces, + } } - fn vertices(&self) -> Vertices { - todo!() + fn bounding_volume(&self) -> Aabb<3> { + transform(self).transform_aabb(&self.shape.bounding_volume()) } } diff --git a/src/kernel/shapes/union.rs b/src/kernel/shapes/union.rs index 1d49232ce..7fa6f5034 100644 --- a/src/kernel/shapes/union.rs +++ b/src/kernel/shapes/union.rs @@ -1,23 +1,15 @@ use crate::{ debug::DebugInfo, - kernel::{ - topology::{edges::Edges, faces::Faces, vertices::Vertices}, - Shape, - }, + kernel::topology::{edges::Edges, faces::Faces, vertices::Vertices, Shape}, math::{Aabb, Scalar}, }; -impl Shape for fj::Union { - fn bounding_volume(&self) -> Aabb<3> { - let a = self.a.bounding_volume(); - let b = self.b.bounding_volume(); - - a.merged(&b) - } +use super::ToShape; - fn faces(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Faces { - let a = self.a.faces(tolerance, debug_info); - let b = self.b.faces(tolerance, debug_info); +impl ToShape for fj::Union { + fn to_shape(&self, tolerance: Scalar, debug_info: &mut DebugInfo) -> Shape { + let a = self.a.to_shape(tolerance, debug_info).faces; + let b = self.b.to_shape(tolerance, debug_info).faces; // This doesn't create a true union, as it doesn't eliminate, merge, or // split faces. @@ -28,14 +20,19 @@ impl Shape for fj::Union { faces.extend(a.0); faces.extend(b.0); - Faces(faces) - } + let faces = Faces(faces); - fn edges(&self) -> Edges { - todo!() + Shape { + vertices: Vertices(Vec::new()), + edges: Edges { cycles: Vec::new() }, + faces, + } } - fn vertices(&self) -> Vertices { - todo!() + fn bounding_volume(&self) -> Aabb<3> { + let a = self.a.bounding_volume(); + let b = self.b.bounding_volume(); + + a.merged(&b) } } diff --git a/src/kernel/topology/mod.rs b/src/kernel/topology/mod.rs index f984bfe9f..8759d4985 100644 --- a/src/kernel/topology/mod.rs +++ b/src/kernel/topology/mod.rs @@ -1,3 +1,12 @@ pub mod edges; pub mod faces; pub mod vertices; + +use self::{edges::Edges, faces::Faces, vertices::Vertices}; + +/// A placeholder struct that will be filled with life later +pub struct Shape { + pub vertices: Vertices, + pub edges: Edges, + pub faces: Faces, +} diff --git a/src/kernel/topology/vertices.rs b/src/kernel/topology/vertices.rs index 736dc5ee4..fca849eb4 100644 --- a/src/kernel/topology/vertices.rs +++ b/src/kernel/topology/vertices.rs @@ -4,6 +4,7 @@ use crate::{ }; /// The vertices of a shape +#[derive(Clone)] pub struct Vertices(pub Vec>); /// A vertex diff --git a/src/main.rs b/src/main.rs index 1404fdd87..1f0ebf8c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ use crate::{ camera::Camera, debug::DebugInfo, graphics::{DrawConfig, Renderer}, - kernel::Shape as _, + kernel::shapes::ToShape as _, mesh::MeshMaker, model::Model, window::Window, @@ -87,7 +87,7 @@ fn main() -> anyhow::Result<()> { }; let mut debug_info = DebugInfo::new(); - let faces = shape.faces(tolerance, &mut debug_info); + let faces = shape.to_shape(tolerance, &mut debug_info).faces; let mut triangles = Vec::new(); faces.triangles(tolerance, &mut triangles, &mut debug_info); @@ -213,7 +213,7 @@ fn main() -> anyhow::Result<()> { debug_info.clear(); triangles.clear(); - let faces = shape.faces(tolerance, &mut debug_info); + let faces = shape.to_shape(tolerance, &mut debug_info).faces; aabb = shape.bounding_volume(); faces.triangles(tolerance, &mut triangles, &mut debug_info);