diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index df614760..ab31b9b0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -40,7 +40,7 @@ jobs: matrix: os: [macOS-latest, ubuntu-latest, windows-latest] toolchain: - - 1.43.0 # Minimum. + - 1.51.0 # Minimum. - stable - beta - nightly @@ -58,4 +58,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path=plexus/Cargo.toml --all-features --verbose \ No newline at end of file + args: --manifest-path=plexus/Cargo.toml --all-features --verbose diff --git a/pictor/src/pipeline.rs b/pictor/src/pipeline.rs index 62a9aa03..30e7405f 100644 --- a/pictor/src/pipeline.rs +++ b/pictor/src/pipeline.rs @@ -93,12 +93,12 @@ where } } -impl Into> for Color4 +impl From> for Vector4 where T: Scalar, { - fn into(self) -> Vector4 { - self.0 + fn from(color: Color4) -> Self { + color.0 } } @@ -340,14 +340,15 @@ impl Application for RenderApplication { fn react(&mut self, event: WindowEvent) -> Reaction { match event { - WindowEvent::KeyboardInput { input, .. } => match input { - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Escape), - state: ElementState::Pressed, - .. - } => Abort, - _ => Continue, - }, + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + state: ElementState::Pressed, + .. + }, + .. + } => Abort, _ => Continue, } } diff --git a/plexus/Cargo.toml b/plexus/Cargo.toml index 728d8f3b..b91dddd2 100644 --- a/plexus/Cargo.toml +++ b/plexus/Cargo.toml @@ -48,9 +48,9 @@ geometry-ultraviolet = ["theon/geometry-ultraviolet"] unstable = [] [dependencies] -approx = "^0.3.0" +approx = "^0.4.0" ahash = "^0.6.0" -arrayvec = "^0.5.0" +arrayvec = "^0.6.0" decorum = "^0.3.1" derivative = "^2.1.1" itertools = "^0.9.0" diff --git a/plexus/src/buffer/builder.rs b/plexus/src/buffer/builder.rs index 30c2e41b..0365976d 100644 --- a/plexus/src/buffer/builder.rs +++ b/plexus/src/buffer/builder.rs @@ -1,9 +1,10 @@ use num::{Integer, NumCast, Unsigned}; use std::hash::Hash; -use typenum::{self, NonZero}; +use typenum::NonZero; use crate::buffer::{BufferError, MeshBuffer}; use crate::builder::{FacetBuilder, MeshBuilder, SurfaceBuilder}; +use crate::constant::{Constant, ToType, TypeOf}; use crate::geometry::{FromGeometry, IntoGeometry}; use crate::index::{Flat, Grouping, IndexBuffer}; use crate::primitive::Topological; @@ -46,11 +47,12 @@ where type Input = (); } -impl FacetBuilder for BufferBuilder, G> +impl FacetBuilder for BufferBuilder, G> where - A: NonZero + typenum::Unsigned, - N: Copy + Hash + Integer + Unsigned, - Vec: IndexBuffer>, + Constant: ToType, + TypeOf: NonZero, + K: Copy + Hash + Integer + Unsigned, + Vec: IndexBuffer>, { type Facet = (); type Key = (); @@ -58,16 +60,19 @@ where fn insert_facet(&mut self, keys: T, _: U) -> Result where Self::Facet: FromGeometry, - T: AsRef<[N]>, + T: AsRef<[K]>, { let keys = keys.as_ref(); - if keys.len() == A::USIZE { + if keys.len() == N { self.indices.extend(keys.iter()); Ok(()) } else { + // TODO: These numbers do not necessarily represent arity (i.e., the + // number of edges of each topological structure). Use a + // different error variant to express this. Err(BufferError::ArityConflict { - expected: A::USIZE, + expected: N, actual: keys.len(), }) } diff --git a/plexus/src/buffer/mod.rs b/plexus/src/buffer/mod.rs index a0165ad6..1d6c132c 100644 --- a/plexus/src/buffer/mod.rs +++ b/plexus/src/buffer/mod.rs @@ -92,12 +92,13 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::FromIterator; use std::vec; -use theon::adjunct::{FromItems, Map}; +use theon::adjunct::Map; use thiserror::Error; use typenum::{self, NonZero, Unsigned as _, U3, U4}; use crate::buffer::builder::BufferBuilder; use crate::builder::{Buildable, MeshBuilder}; +use crate::constant::{Constant, ToType, TypeOf}; use crate::encoding::{FaceDecoder, FromEncoding, VertexDecoder}; use crate::geometry::{FromGeometry, IntoGeometry}; use crate::index::{ @@ -109,7 +110,7 @@ use crate::primitive::{ BoundedPolygon, IntoIndexed, IntoPolygons, Polygonal, Tetragon, Topological, Trigon, UnboundedPolygon, }; -use crate::{Arity, DynamicArity, MeshArity, Monomorphic, StaticArity}; +use crate::{Arity, DynamicArity, MeshArity, Monomorphic, StaticArity, TryFromIterator}; /// Errors concerning raw buffers and [`MeshBuffer`]s. /// @@ -223,13 +224,14 @@ pub trait FromRawBuffersWithArity: Sized { // TODO: Provide a similar trait for index buffers instead. `MeshBuffer` could // use such a trait to provide this API. -pub trait IntoFlatIndex +pub trait IntoFlatIndex where - A: NonZero + typenum::Unsigned, + Constant: ToType, + TypeOf: NonZero, { type Item: Copy + Integer + Unsigned; - fn into_flat_index(self) -> MeshBuffer, G>; + fn into_flat_index(self) -> MeshBuffer, G>; } // TODO: Provide a similar trait for index buffers instead. `MeshBuffer` could @@ -258,7 +260,7 @@ where /// The `R` type parameter specifies the [`Grouping`] of the index buffer. See /// the [`index`] module documention for more information. /// -/// [`Grouping`]: crate::buffer::Grouping +/// [`Grouping`]: crate::index::Grouping /// [`index`]: crate::index #[derive(Debug)] pub struct MeshBuffer @@ -419,15 +421,16 @@ where } } -impl DynamicArity for MeshBuffer, G> +impl DynamicArity for MeshBuffer, G> where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { - type Dynamic = as StaticArity>::Static; + type Dynamic = as StaticArity>::Static; fn arity(&self) -> Self::Dynamic { - Flat::::ARITY + Flat::::ARITY } } @@ -476,10 +479,11 @@ where const ARITY: Self::Static = R::ARITY; } -impl MeshBuffer, G> +impl MeshBuffer, G> where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + NumCast + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + NumCast + Unsigned, { /// Appends the contents of a flat `MeshBuffer` into another `MeshBuffer`. /// The source buffer is drained. @@ -491,9 +495,9 @@ where where G: FromGeometry, R: Grouping, - R::Group: Into< as Grouping>::Group>, + R::Group: Into< as Grouping>::Group>, { - let offset = N::from(self.vertices.len()).ok_or(BufferError::IndexOverflow)?; + let offset = T::from(self.vertices.len()).ok_or(BufferError::IndexOverflow)?; self.vertices.extend( buffer .vertices @@ -637,11 +641,12 @@ where } } -impl FromRawBuffers for MeshBuffer, G> +impl FromRawBuffers for MeshBuffer, G> where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + NumCast + Unsigned, - M: Copy + Integer + NumCast + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + NumCast + Unsigned, + U: Copy + Integer + NumCast + Unsigned, G: FromGeometry, { type Error = BufferError; @@ -686,14 +691,14 @@ where /// ``` fn from_raw_buffers(indices: I, vertices: J) -> Result where - I: IntoIterator, + I: IntoIterator, J: IntoIterator, { let indices = indices .into_iter() - .map(|index| ::from(index).ok_or(BufferError::IndexOverflow)) + .map(|index| ::from(index).ok_or(BufferError::IndexOverflow)) .collect::, _>>()?; - if indices.len() % A::USIZE != 0 { + if indices.len() % N != 0 { Err(BufferError::IndexUnaligned) } else { @@ -701,7 +706,7 @@ where .into_iter() .map(|vertex| vertex.into_geometry()) .collect(); - let len = N::from(vertices.len()).unwrap(); + let len = T::from(vertices.len()).unwrap(); if indices.iter().any(|index| *index >= len) { Err(BufferError::IndexOutOfBounds) } @@ -774,19 +779,20 @@ where } } -impl IntoFlatIndex for MeshBuffer, G> +impl IntoFlatIndex for MeshBuffer, G> where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { - type Item = N; + type Item = T; - fn into_flat_index(self) -> MeshBuffer, G> { + fn into_flat_index(self) -> MeshBuffer, G> { self } } -impl IntoFlatIndex for MeshBuffer, G> +impl IntoFlatIndex for MeshBuffer, G> where N: Copy + Integer + Unsigned, { @@ -821,7 +827,7 @@ where /// // ... /// } /// ``` - fn into_flat_index(self) -> MeshBuffer, G> { + fn into_flat_index(self) -> MeshBuffer, G> { let MeshBuffer { indices, vertices } = self; MeshBuffer { indices: indices @@ -833,7 +839,7 @@ where } } -impl IntoFlatIndex for MeshBuffer, G> +impl IntoFlatIndex for MeshBuffer, G> where N: Copy + Integer + Unsigned, { @@ -868,7 +874,7 @@ where /// // ... /// } /// ``` - fn into_flat_index(self) -> MeshBuffer, G> { + fn into_flat_index(self) -> MeshBuffer, G> { let MeshBuffer { indices, vertices } = self; MeshBuffer { indices: indices @@ -901,7 +907,7 @@ where .into_iter() .map(|chunk| { // These conversions should never fail. - Trigon::from_items(chunk.map(|index| { + Trigon::try_from_iter(chunk.map(|index| { let index = ::from(index).expect("index overflow"); vertices[index].clone() })) @@ -960,7 +966,7 @@ where .into_iter() .map(|chunk| { // These conversions should never fail. - Tetragon::from_items(chunk.map(|index| { + Tetragon::try_from_iter(chunk.map(|index| { let index = ::from(index).expect("index overflow"); vertices[index].clone() })) @@ -1086,8 +1092,8 @@ where .into_iter() .chunks(U3::USIZE) .into_iter() - .map(::Group::from_items) - .collect::>>() + .map(::Group::try_from_iter) + .collect::, _>>() .expect("inconsistent index buffer"); MeshBuffer { indices, vertices } } @@ -1134,8 +1140,8 @@ where .into_iter() .chunks(U4::USIZE) .into_iter() - .map(::Group::from_items) - .collect::>>() + .map(::Group::try_from_iter) + .collect::, _>>() .expect("inconsistent index buffer"); MeshBuffer { indices, vertices } } diff --git a/plexus/src/constant.rs b/plexus/src/constant.rs new file mode 100644 index 00000000..94571edd --- /dev/null +++ b/plexus/src/constant.rs @@ -0,0 +1,44 @@ +//! Morphisms between constant generics and numeric types. +//! +//! This module provides conversions between `typenum`'s unsigned integer types +//! and `usize` constant generics. These conversions are necessary to perform +//! static computations and comparisons, which cannot yet be done using constant +//! generics alone at the time of writing (e.g., `{N >= 3}`). +//! +//! See discussion on the [Rust internals +//! forum](https://internals.rust-lang.org/t/const-generics-where-restrictions/12742/7). + +// TODO: Move this into the `theon` crate as part of its public API. + +pub type ConstantOf = ::Output; +pub type TypeOf = as ToType>::Output; + +pub struct Constant; + +pub trait ToConstant { + type Output; +} + +pub trait ToType { + type Output; +} + +macro_rules! impl_morphisms { + (types => $($n:ident),*$(,)?) => ( + use typenum::Unsigned; + + $( + impl ToConstant for typenum::$n { + type Output = Constant<{ typenum::$n::USIZE }>; + } + + impl ToType for Constant<{ typenum::$n::USIZE }> { + type Output = typenum::$n; + } + )* + ); +} +impl_morphisms!(types => + U0, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18, U19, U21, + U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32, +); diff --git a/plexus/src/graph/edge.rs b/plexus/src/graph/edge.rs index 382e2303..c29236e6 100644 --- a/plexus/src/graph/edge.rs +++ b/plexus/src/graph/edge.rs @@ -118,9 +118,9 @@ impl From<(VertexKey, VertexKey)> for ArcKey { } } -impl Into<(VertexKey, VertexKey)> for ArcKey { - fn into(self) -> (VertexKey, VertexKey) { - (self.0, self.1) +impl From for (VertexKey, VertexKey) { + fn from(key: ArcKey) -> Self { + (key.0, key.1) } } @@ -1067,29 +1067,29 @@ where } } -impl Hash for ArcView +impl From> for View> where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - self.inner.hash(state); + fn from(arc: ArcView) -> Self { + let ArcView { inner, .. } = arc; + inner } } -impl Into>> for ArcView +impl Hash for ArcView where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn into(self) -> View> { - let ArcView { inner, .. } = self; - inner + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.inner.hash(state); } } @@ -1483,29 +1483,29 @@ where } } -impl Hash for EdgeView +impl From> for View> where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - self.inner.hash(state); + fn from(edge: EdgeView) -> Self { + let EdgeView { inner, .. } = edge; + inner } } -impl Into>> for EdgeView +impl Hash for EdgeView where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn into(self) -> View> { - let EdgeView { inner, .. } = self; - inner + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.inner.hash(state); } } @@ -1635,7 +1635,7 @@ where B::Target: AsStorage::Data>> + Parametric, { storage: B, - inner: as IntoIterator>::IntoIter, + inner: as IntoIterator>::IntoIter, } impl VertexCirculator @@ -1731,7 +1731,7 @@ where B::Target: AsStorage::Data>> + Parametric, { storage: B, - inner: as IntoIterator>::IntoIter, + inner: as IntoIterator>::IntoIter, } impl FaceCirculator @@ -1784,7 +1784,7 @@ where .and_then(|opposite| opposite.face) .into_iter(), ) - .collect::>() + .collect::>() .into_iter(); let (storage, _) = arc.unbind(); FaceCirculator { storage, inner } diff --git a/plexus/src/graph/face.rs b/plexus/src/graph/face.rs index b90602b7..72083389 100644 --- a/plexus/src/graph/face.rs +++ b/plexus/src/graph/face.rs @@ -993,29 +993,29 @@ where } } -impl Hash for FaceView +impl From> for View> where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - self.inner.hash(state); + fn from(face: FaceView) -> Self { + let FaceView { inner, .. } = face; + inner } } -impl Into>> for FaceView +impl Hash for FaceView where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn into(self) -> View> { - let FaceView { inner, .. } = self; - inner + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.inner.hash(state); } } diff --git a/plexus/src/graph/geometry.rs b/plexus/src/graph/geometry.rs index 52e5a210..c4ae958c 100644 --- a/plexus/src/graph/geometry.rs +++ b/plexus/src/graph/geometry.rs @@ -7,7 +7,6 @@ // necessary, constraints are specified there so that they do not pollute user // code. -use theon::adjunct::FromItems; use theon::ops::{Cross, Interpolate, Project}; use theon::query::Plane; use theon::space::{EuclideanSpace, FiniteDimensional, InnerSpace, Vector, VectorSpace}; @@ -21,7 +20,8 @@ use crate::graph::edge::{Arc, ArcView, Edge, ToArc}; use crate::graph::face::{Face, ToRing}; use crate::graph::mutation::Consistent; use crate::graph::vertex::{Vertex, VertexView}; -use crate::graph::{GraphError, OptionExt as _}; +use crate::graph::{GraphError, OptionExt as _, ResultExt as _}; +use crate::IteratorExt as _; pub type VertexPosition = Position<::Vertex>; @@ -120,9 +120,11 @@ where B::Target: AsStorage> + AsStorage> + Consistent + Parametric, { - let (a, b) = - FromItems::from_items(arc.adjacent_vertices().map(|vertex| *vertex.position())) - .expect_consistent(); + let (a, b) = arc + .adjacent_vertices() + .map(|vertex| *vertex.position()) + .try_collect() + .expect_consistent(); let c = *arc.next_arc().destination_vertex().position(); let ab = a - b; let cb = c - b; @@ -163,9 +165,11 @@ where T: ToArc, { let arc = edge.into_arc(); - let (a, b) = - FromItems::from_items(arc.adjacent_vertices().map(|vertex| *vertex.position())) - .unwrap(); + let (a, b) = arc + .adjacent_vertices() + .map(|vertex| *vertex.position()) + .try_collect() + .expect_consistent(); Ok(a.midpoint(b)) } } @@ -229,9 +233,12 @@ where T: ToRing, { let ring = ring.into_ring(); - let (a, b) = - FromItems::from_items(ring.vertices().take(2).map(|vertex| *vertex.position())) - .expect_consistent(); + let (a, b) = ring + .vertices() + .take(2) + .map(|vertex| *vertex.position()) + .try_collect() + .expect_consistent(); let c = G::centroid(ring)?; let ab = a - b; let bc = b - c; diff --git a/plexus/src/graph/mod.rs b/plexus/src/graph/mod.rs index 5a95e316..273dcaf3 100644 --- a/plexus/src/graph/mod.rs +++ b/plexus/src/graph/mod.rs @@ -257,7 +257,6 @@ mod vertex; use decorum::cmp::IntrinsicOrd; use decorum::R64; -use itertools::Itertools; use num::{Integer, NumCast, ToPrimitive, Unsigned}; use smallvec::SmallVec; use std::borrow::Borrow; @@ -267,15 +266,16 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::FromIterator; use std::vec; -use theon::adjunct::{FromItems, Map}; +use theon::adjunct::Map; use theon::query::Aabb; use theon::space::{EuclideanSpace, Scalar}; use theon::{AsPosition, AsPositionMut}; use thiserror::Error; -use typenum::{self, NonZero}; +use typenum::NonZero; use crate::buffer::{BufferError, FromRawBuffers, FromRawBuffersWithArity, MeshBuffer}; use crate::builder::{Buildable, FacetBuilder, MeshBuilder, SurfaceBuilder}; +use crate::constant::{Constant, ToType, TypeOf}; use crate::encoding::{FaceDecoder, FromEncoding, VertexDecoder}; use crate::entity::storage::prelude::*; use crate::entity::storage::{AsStorage, AsStorageMut, AsStorageOf, Key, StorageTarget}; @@ -1283,6 +1283,16 @@ where } } +impl From> for OwnedCore +where + G: GraphData, +{ + fn from(graph: MeshGraph) -> Self { + let MeshGraph { core, .. } = graph; + core + } +} + impl FromEncoding for MeshGraph where E: FaceDecoder + VertexDecoder, @@ -1450,6 +1460,8 @@ where I: IntoIterator, J: IntoIterator, { + use itertools::Itertools; + if arity < 3 { return Err(GraphError::ArityNonPolygonal); } @@ -1482,23 +1494,6 @@ where } } -impl Parametric for MeshGraph -where - G: GraphData, -{ - type Data = G; -} - -impl Into> for MeshGraph -where - G: GraphData, -{ - fn into(self) -> OwnedCore { - let MeshGraph { core, .. } = self; - core - } -} - impl IntoPolygons for MeshGraph where G: GraphData, @@ -1507,18 +1502,29 @@ where type Polygon = UnboundedPolygon; fn into_polygons(self) -> Self::Output { + use crate::IteratorExt as _; + self.faces() .map(|face| { // The arity of a face in a graph must be polygonal (three or // higher) so this should never fail. - let vertices = face.adjacent_vertices().map(|vertex| vertex.get().clone()); - UnboundedPolygon::from_items(vertices).expect_consistent() + face.adjacent_vertices() + .map(|vertex| vertex.get().clone()) + .try_collect() + .expect_consistent() }) .collect::>() .into_iter() } } +impl Parametric for MeshGraph +where + G: GraphData, +{ + type Data = G; +} + impl StaticArity for MeshGraph where G: GraphData, @@ -1528,10 +1534,11 @@ where const ARITY: Self::Static = (3, None); } -impl TryFrom, H>> for MeshGraph +impl TryFrom, H>> for MeshGraph where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + NumCast + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + NumCast + Unsigned, H: Clone, G: GraphData, G::Vertex: FromGeometry, @@ -1571,7 +1578,7 @@ where /// /// [`MeshBuffer`]: crate::buffer::MeshBuffer /// [`MeshGraph`]: crate::graph::MeshGraph - fn try_from(buffer: MeshBuffer, H>) -> Result { + fn try_from(buffer: MeshBuffer, H>) -> Result { let arity = buffer.arity(); let (indices, vertices) = buffer.into_raw_buffers(); MeshGraph::from_raw_buffers_with_arity(indices, vertices, arity) diff --git a/plexus/src/graph/vertex.rs b/plexus/src/graph/vertex.rs index 81bd5ccb..be6e46db 100644 --- a/plexus/src/graph/vertex.rs +++ b/plexus/src/graph/vertex.rs @@ -689,29 +689,29 @@ where } } -impl Hash for VertexView +impl From> for View> where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - self.inner.hash(state); + fn from(vertex: VertexView) -> Self { + let VertexView { inner, .. } = vertex; + inner } } -impl Into>> for VertexView +impl Hash for VertexView where B: Reborrow, M: AsStorage> + Parametric, G: GraphData, { - fn into(self) -> View> { - let VertexView { inner, .. } = self; - inner + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.inner.hash(state); } } diff --git a/plexus/src/index.rs b/plexus/src/index.rs index f38748c6..0d3253e2 100644 --- a/plexus/src/index.rs +++ b/plexus/src/index.rs @@ -84,8 +84,9 @@ use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; use theon::adjunct::Map; -use typenum::{NonZero, U3, U4}; +use typenum::NonZero; +use crate::constant::{Constant, ToType, TypeOf}; use crate::primitive::decompose::IntoVertices; use crate::primitive::Topological; use crate::{Monomorphic, StaticArity}; @@ -118,12 +119,13 @@ where type Index: Copy + Integer + Unsigned; } -impl IndexBuffer> for Vec +impl IndexBuffer> for Vec where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { - type Index = N; + type Index = T; } impl

IndexBuffer

for Vec

(polygon: &P) -> impl '_ + Clone + Iterator>> where P: Polygonal, P::Vertex: AsPosition, diff --git a/plexus/src/primitive/sphere.rs b/plexus/src/primitive/sphere.rs index 51d2d2ef..7002ca81 100644 --- a/plexus/src/primitive/sphere.rs +++ b/plexus/src/primitive/sphere.rs @@ -318,7 +318,6 @@ where mod tests { use nalgebra::Point3; use std::collections::BTreeSet; - use std::iter::FromIterator; use crate::prelude::*; use crate::primitive::generate::Position; @@ -351,12 +350,11 @@ mod tests { fn position_index_to_vertex_mapping() { assert_eq!( 5, - BTreeSet::from_iter( - UvSphere::new(3, 2) - .indexing_polygons::() // 18 vertices, 5 indices. - .vertices() - ) - .len() + UvSphere::new(3, 2) + .indexing_polygons::() // 18 vertices, 5 indices. + .vertices() + .collect::>() + .len() ) } }

@@ -143,11 +145,12 @@ where fn push(&mut self, index: P); } -impl Push, P> for Vec +impl Push, P> for Vec where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, - P: Monomorphic + IntoVertices + Topological, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, + P: Monomorphic + IntoVertices + Topological, { fn push(&mut self, index: P) { for index in index.into_vertices() { @@ -174,13 +177,18 @@ pub trait Grouping: StaticArity { /// Flat index buffer meta-grouping. /// -/// Describes a flat index buffer with a constant arity. Arity is specified -/// using a type constant from the [`typenum`] crate. +/// Describes a flat index buffer with a constant arity. The number of vertices +/// in the indexed topological structures is specified using a constant +/// parameter `N`, which represents the number of grouped elements in the index +/// buffer. For example, `Flat<_, 3>` describes an index buffer with indices in +/// implicit and contiguous groups of three. Note that this constant may be +/// distinct from the arity of the indexed topological structures (i.e., if `N` +/// is less than three, then arity is `N - 1` and may be zero.). /// /// Unlike structured groupings, this meta-grouping is needed to associate an -/// index type with an arity. For example, `Vec` implements both -/// `IndexBuffer>` (a triangular buffer) and -/// `IndexBuffer>` (a quadrilateral buffer). +/// index type with an implicit grouping and arity. For example, `Vec` +/// implements both `IndexBuffer>` (a triangular buffer) and +/// `IndexBuffer>` (a quadrilateral buffer). /// /// See the [`index`] module documention for more information about index /// buffers. @@ -193,55 +201,56 @@ pub trait Grouping: StaticArity { /// use plexus::buffer::MeshBuffer; /// use plexus::index::Flat; /// use plexus::prelude::*; -/// use plexus::U3; /// -/// let mut buffer = MeshBuffer::, (f64, f64, f64)>::default(); +/// let mut buffer = MeshBuffer::, (f64, f64, f64)>::default(); /// ``` /// -/// [`typenum`]: https://crates.io/crates/typenum -/// /// [`MeshBuffer`]: crate::buffer::MeshBuffer /// [`index`]: crate::index #[derive(Debug)] -pub struct Flat +pub struct Flat where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { - phantom: PhantomData<(A, N)>, + phantom: PhantomData, } -impl Grouping for Flat +impl Grouping for Flat where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { /// The elements of flat index buffers are indices. These indices are - /// implicitly grouped by the arity of the buffer (`A`). - type Group = N; + /// implicitly grouped by the arity of the buffer (`N`). + type Group = T; } -impl Monomorphic for Flat +impl Monomorphic for Flat where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { } -impl StaticArity for Flat +impl StaticArity for Flat where - A: NonZero + typenum::Unsigned, - N: Copy + Integer + Unsigned, + Constant: ToType, + TypeOf: NonZero, + T: Copy + Integer + Unsigned, { type Static = usize; - const ARITY: Self::Static = A::USIZE; + const ARITY: Self::Static = crate::n_arity(N); } /// Alias for a flat and triangular index buffer. -pub type Flat3 = Flat; +pub type Flat3 = Flat; /// Alias for a flat and quadrilateral index buffer. -pub type Flat4 = Flat; +pub type Flat4 = Flat; /// Structured index buffer grouping. /// diff --git a/plexus/src/lib.rs b/plexus/src/lib.rs index 44600cb7..2955c15b 100644 --- a/plexus/src/lib.rs +++ b/plexus/src/lib.rs @@ -18,6 +18,7 @@ pub mod buffer; pub mod builder; +pub mod constant; pub mod encoding; mod entity; pub mod geometry; @@ -27,14 +28,13 @@ pub mod integration; pub mod primitive; mod transact; +use arrayvec::ArrayVec; use itertools::{self, Itertools, MinMaxResult, MultiPeek}; use std::borrow::Borrow; use std::fmt::Debug; use crate::entity::view::ClosedView; -pub use typenum::{U2, U3, U4}; - pub mod prelude { //! Re-exports of commonly used types and traits. //! @@ -238,6 +238,66 @@ impl Arity for MeshArity { } } +pub trait TryFromIterator: Sized { + type Error; + + fn try_from_iter(items: I) -> Result + where + I: Iterator; +} + +impl TryFromIterator for [T; N] { + type Error = (); + + fn try_from_iter(items: I) -> Result + where + I: Iterator, + { + items + .has_exactly(N) + .and_then(|items| items.collect::>().into_inner().ok()) + .ok_or(()) + } +} + +macro_rules! count { + ($x:tt $($xs:tt)*) => (1usize + count!($($xs)*)); + () => (0usize); +} +macro_rules! substitute { + ($_t:tt, $with:ty) => { + $with + }; +} +macro_rules! impl_try_from_iterator { + (tuples => ($($i:ident),+)) => ( + #[allow(non_snake_case)] + impl TryFromIterator for ($(substitute!(($i), T),)+) { + type Error = (); + + fn try_from_iter(items: I) -> Result + where + I: Iterator, + { + use $crate::IteratorExt as _; + + items + .has_exactly(count!($($i)*)) + .map(|mut items| { + $(let $i = items.next().unwrap();)* + ($($i,)*) + }) + .ok_or(()) + } + } + ); +} +impl_try_from_iterator!(tuples => (A, B)); +impl_try_from_iterator!(tuples => (A, B, C)); +impl_try_from_iterator!(tuples => (A, B, C, D)); +impl_try_from_iterator!(tuples => (A, B, C, D, E)); +impl_try_from_iterator!(tuples => (A, B, C, D, E, F)); + /// Extension methods for types implementing [`Iterator`]. /// /// [`Iterator`]: std::iter::Iterator @@ -329,12 +389,26 @@ pub trait IteratorExt: Iterator + Sized { /// } /// ``` fn has_at_least(self, n: usize) -> Option> { - let mut peekable = itertools::multipeek(self); - for _ in 0..n { - peekable.peek()?; - } - peekable.reset_peek(); - Some(peekable) + peek_n(self, n).map(|mut peekable| { + peekable.reset_peek(); + peekable + }) + } + + fn has_exactly(self, n: usize) -> Option> { + peek_n(self, n).and_then(|mut peekable| { + peekable.peek().is_none().then(|| { + peekable.reset_peek(); + peekable + }) + }) + } + + fn try_collect(self) -> Result + where + T: TryFromIterator, + { + T::try_from_iter(self) } } @@ -386,13 +460,13 @@ where fn next(&mut self) -> Option { let next = self.input.next(); - match (self.previous.clone(), next.or_else(|| self.first.take())) { - (Some(a), Some(b)) => { + self.previous + .clone() + .zip(next.or_else(|| self.first.take())) + .map(|(a, b)| { self.previous = Some(b.clone()); - Some((a, b)) - } - _ => None, - } + (a, b) + }) } fn size_hint(&self) -> (usize, Option) { @@ -440,3 +514,29 @@ where self.input.size_hint() } } + +fn peek_n(input: I, n: usize) -> Option> +where + I: Iterator, +{ + let mut peekable = itertools::multipeek(input); + for _ in 0..n { + peekable.peek()?; + } + Some(peekable) +} + +/// Computes the arity of a polygon with `n` vertices. +/// +/// For `n` greater than two, these values are the same and well-formed. For `n` +/// less than three, the polygon is degenerate (a digon, monogon, or zerogon), +/// all of which are assigned an arity of one. Note that some topological types +/// do not allow `n` being one nor zero. +const fn n_arity(n: usize) -> usize { + if n < 3 { + 1 + } + else { + n + } +} diff --git a/plexus/src/primitive/cube.rs b/plexus/src/primitive/cube.rs index da2520b2..dae0abdd 100644 --- a/plexus/src/primitive/cube.rs +++ b/plexus/src/primitive/cube.rs @@ -33,12 +33,12 @@ use crate::primitive::Tetragon; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Plane { - XY, // front - NXY, // back - YZ, // right - NYZ, // left - XZ, // bottom - NXZ, // top + Xy, // front + Nxy, // back + Yz, // right + Nyz, // left + Xz, // bottom + Nxz, // top } impl Plane { @@ -50,12 +50,12 @@ impl Plane { S: Basis + FiniteDimensional + InnerSpace, { match *self { - Plane::XY => Unit::::z(), // front - Plane::NXY => -Unit::::z(), // back - Plane::YZ => Unit::::x(), // right - Plane::NYZ => -Unit::::x(), // left - Plane::XZ => -Unit::::y(), // bottom - Plane::NXZ => Unit::::y(), // top + Plane::Xy => Unit::::z(), // front + Plane::Nxy => -Unit::::z(), // back + Plane::Yz => Unit::::x(), // right + Plane::Nyz => -Unit::::x(), // left + Plane::Xz => -Unit::::y(), // bottom + Plane::Nxz => Unit::::y(), // top } } } @@ -249,12 +249,12 @@ impl AttributeVertexGenerator for Cube { fn vertex_from(&self, _: &Self::State, index: usize) -> Self::Output { match index { - 0 => Plane::XY, // front - 1 => Plane::YZ, // right - 2 => Plane::NXZ, // top - 3 => Plane::NYZ, // left - 4 => Plane::XZ, // bottom - 5 => Plane::NXY, // back + 0 => Plane::Xy, // front + 1 => Plane::Yz, // right + 2 => Plane::Nxz, // top + 3 => Plane::Nyz, // left + 4 => Plane::Xz, // bottom + 5 => Plane::Nxy, // back _ => panic!(), } } diff --git a/plexus/src/primitive/decompose.rs b/plexus/src/primitive/decompose.rs index 0725f8d6..5f2ce314 100644 --- a/plexus/src/primitive/decompose.rs +++ b/plexus/src/primitive/decompose.rs @@ -70,11 +70,12 @@ use arrayvec::ArrayVec; use std::collections::VecDeque; use std::iter::IntoIterator; -use theon::adjunct::IntoItems; use theon::ops::Interpolate; +use typenum::{Cmp, Greater, U1}; +use crate::constant::{Constant, ToType, TypeOf}; use crate::primitive::{ - BoundedPolygon, Edge, Polygonal, Tetragon, Topological, Trigon, UnboundedPolygon, + BoundedPolygon, Edge, NGon, Polygonal, Tetragon, Topological, Trigon, UnboundedPolygon, }; use crate::IteratorExt as _; @@ -172,16 +173,15 @@ pub trait IntoVertices: Topological { impl IntoVertices for T where - T: IntoItems + Topological, + T: Topological, { - type Output = ::Output; + type Output = ::IntoIter; fn into_vertices(self) -> Self::Output { - self.into_items() + self.into_iter() } } -// TODO: Use a macro to implement this for all `NGon`s. pub trait IntoEdges: Topological { type Output: IntoIterator>; @@ -201,47 +201,35 @@ pub trait IntoSubdivisions: Polygonal { } pub trait IntoTetrahedrons: Polygonal { - fn into_tetrahedrons(self) -> ArrayVec<[Trigon; 4]>; -} - -impl IntoEdges for Edge { - type Output = Option>; - - fn into_edges(self) -> Self::Output { - Some(self) - } -} - -impl IntoEdges for Trigon -where - T: Clone, -{ - type Output = ArrayVec<[Edge; 3]>; - - fn into_edges(self) -> Self::Output { - let [a, b, c] = self.into_array(); - ArrayVec::from([ - Edge::new(a.clone(), b.clone()), - Edge::new(b, c.clone()), - Edge::new(c, a), - ]) - } + fn into_tetrahedrons(self) -> ArrayVec, 4>; } -impl IntoEdges for Tetragon +impl IntoEdges for NGon where - T: Clone, + G: Clone, + Constant: ToType, + TypeOf: Cmp, { - type Output = ArrayVec<[Edge; 4]>; + // TODO: As of Rust 1.51.1, it is not possible to constrain constant + // generics nor use them in expressions. If and when this is possible, + // do not implement this trait for degenerate `NGon`s and use use an + // `ArrayVec, {if N == 2 { 1 } else { N }}>` as + // output. + type Output = Vec>; fn into_edges(self) -> Self::Output { - let [a, b, c, d] = self.into_array(); - ArrayVec::from([ - Edge::new(a.clone(), b.clone()), - Edge::new(b, c.clone()), - Edge::new(c, d.clone()), - Edge::new(d, a), - ]) + if N == 2 { + // At the time of writing, it is not possible to specialize this + // case for `NGon` nor prove to the compiler that `Self` is of + // type `Edge` when the condition `N == 2` is `true`. + vec![self.into_iter().try_collect().unwrap()] + } + else { + self.into_iter() + .perimeter() + .map(|(a, b)| Edge::new(a, b)) + .collect() + } } } @@ -274,7 +262,7 @@ where } impl IntoTrigons for Trigon { - type Output = ArrayVec<[Trigon; 1]>; + type Output = ArrayVec, 1>; fn into_trigons(self) -> Self::Output { ArrayVec::from([self]) @@ -285,7 +273,7 @@ impl IntoTrigons for Tetragon where T: Clone, { - type Output = ArrayVec<[Trigon; 2]>; + type Output = ArrayVec, 2>; fn into_trigons(self) -> Self::Output { let [a, b, c, d] = self.into_array(); @@ -311,7 +299,7 @@ impl IntoSubdivisions for Trigon where T: Clone + Interpolate, { - type Output = ArrayVec<[Trigon; 2]>; + type Output = ArrayVec, 2>; fn into_subdivisions(self) -> Self::Output { let [a, b, c] = self.into_array(); @@ -324,7 +312,7 @@ impl IntoSubdivisions for Tetragon where T: Clone + Interpolate, { - type Output = ArrayVec<[Tetragon; 4]>; + type Output = ArrayVec, 4>; fn into_subdivisions(self) -> Self::Output { let [a, b, c, d] = self.into_array(); @@ -346,7 +334,7 @@ impl IntoTetrahedrons for Tetragon where T: Clone + Interpolate, { - fn into_tetrahedrons(self) -> ArrayVec<[Trigon; 4]> { + fn into_tetrahedrons(self) -> ArrayVec, 4> { let [a, b, c, d] = self.into_array(); let ac = a.clone().midpoint(c.clone()); // Diagonal. ArrayVec::from([ @@ -451,7 +439,7 @@ where pub trait Tetrahedrons: Sized { #[allow(clippy::type_complexity)] - fn tetrahedrons(self) -> Decompose, Trigon, ArrayVec<[Trigon; 4]>>; + fn tetrahedrons(self) -> Decompose, Trigon, ArrayVec, 4>>; } impl Tetrahedrons for I @@ -460,7 +448,7 @@ where T: Clone + Interpolate, { #[allow(clippy::type_complexity)] - fn tetrahedrons(self) -> Decompose, Trigon, ArrayVec<[Trigon; 4]>> { + fn tetrahedrons(self) -> Decompose, Trigon, ArrayVec, 4>> { Decompose::new(self, Tetragon::into_tetrahedrons) } } diff --git a/plexus/src/primitive/mod.rs b/plexus/src/primitive/mod.rs index 940603b5..46ad651f 100644 --- a/plexus/src/primitive/mod.rs +++ b/plexus/src/primitive/mod.rs @@ -25,7 +25,7 @@ //! //! | Type | Morphism | Arity | Map | Zip | Tessellate | //! |--------------------|-------------|--------------|-----|-----|------------| -//! | `NGon` | Monomorphic | $[2,12]$ | Yes | Yes | Yes | +//! | `NGon` | Monomorphic | $1,[3,32]$ | Yes | Yes | Yes | //! | `BoundedPolygon` | Polymorphic | $[3,4]$ | Yes | No | Yes | //! | `UnboundedPolygon` | Polymorphic | $[3,\infin)$ | Yes | No | No | //! @@ -43,7 +43,7 @@ //! traits. [`UnboundedPolygon`] is most flexible and can represent any //! arbitrary polygon, but does not support any tessellation features. //! -//! [`Edge`]s are always represented as `NGon<[_; 2]>`. +//! [`Edge`]s are always represented as `NGon<_, 2>`. //! //! # Examples //! @@ -85,13 +85,14 @@ pub mod decompose; pub mod generate; pub mod sphere; -use arrayvec::{Array, ArrayVec}; +use arrayvec::ArrayVec; use decorum::Real; use fool::BoolExt as _; use itertools::izip; use itertools::structs::Zip as OuterZip; // Avoid collision with `Zip`. use num::{Integer, One, Signed, Unsigned, Zero}; use smallvec::{smallvec, SmallVec}; +use std::array; use std::convert::TryInto; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; @@ -101,12 +102,12 @@ use theon::ops::Cross; use theon::query::{Intersection, Line, LineLine, LinePlane, Plane, Unit}; use theon::space::{EuclideanSpace, FiniteDimensional, Scalar, Vector, VectorSpace}; use theon::{AsPosition, AsPositionMut, Position}; -use typenum::type_operators::Cmp; -use typenum::{Greater, U2, U3}; +use typenum::{Cmp, Greater, U1, U2, U3}; +use crate::constant::{Constant, ToType, TypeOf}; use crate::geometry::partition::PointPartition; use crate::primitive::decompose::IntoVertices; -use crate::{DynamicArity, IteratorExt as _, Monomorphic, StaticArity}; +use crate::{DynamicArity, IteratorExt as _, Monomorphic, StaticArity, TryFromIterator}; /// Topological structure. /// @@ -413,62 +414,104 @@ where /// `NGon` represents a polygonal structure as an array. Each array element /// represents vertex data in order with adjacent elements being connected by an /// implicit undirected edge. For example, an `NGon` with three vertices -/// (`NGon<[T; 3]>`) would represent a trigon (triangle). Generally these -/// elements are labeled $A$, $B$, $C$, etc. +/// (`NGon<_, 3>`) would represent a trigon (triangle). Generally these elements +/// are labeled $A$, $B$, $C$, etc. Note that the constant parameter `N` +/// represents the number of the `NGon`'s vertices and **not** the number of its +/// edges (arity). /// /// `NGon`s with less than three vertices are a degenerate case. An `NGon` with -/// two vertices (`NGon<[T; 2]>`) is considered a _monogon_ despite common -/// definitions specifying a single vertex. Such an `NGon` is not considered a -/// _digon_, as it represents a single undirected edge rather than two distinct -/// (but collapsed) edges. Single-vertex `NGon`s are unsupported. See the `Edge` -/// type definition. +/// two vertices (`NGon<_, 2>`) is considered a _monogon_ and is used to +/// represent edges. Such an `NGon` is not considered a _digon_, as it +/// represents a single undirected edge rather than two distinct (but collapsed) +/// edges. Note that the polygonal types [`BoundedPolygon`] and +/// [`UnboundedPolygon`] never represent edges. See the [`Edge`] type +/// definition. +/// +/// `NGon`s with one or zero vertices are unsupported and lack various trait +/// implementations. /// /// See the [module][`primitive`] documentation for more information. /// +/// [`BoundedPolygon`]: crate::primitive::BoundedPolygon +/// [`Edge`]: crate::primitive::Edge /// [`primitive`]: crate::primitive +/// [`UnboundedPolygon`]: crate::primitive::UnboundedPolygon #[derive(Clone, Copy, Debug, PartialEq)] -pub struct NGon(pub A) -where - A: Array; +pub struct NGon(pub [G; N]); -impl NGon -where - A: Array, -{ - pub fn into_array(self) -> A { +impl NGon { + pub fn into_array(self) -> [G; N] { self.0 } + + pub fn positions(&self) -> NGon<&Position, N> + where + G: AsPosition, + { + if let Ok(array) = self + .as_ref() + .iter() + .map(|vertex| vertex.as_position()) + .collect::>() + .into_inner() + { + array.into() + } + else { + panic!() + } + } } -impl AsRef<[::Item]> for NGon -where - A: Array, -{ - fn as_ref(&self) -> &[A::Item] { - self.0.as_slice() +impl<'a, G, const N: usize> NGon<&'a G, N> { + pub fn cloned(self) -> NGon + where + G: Clone, + { + self.map(|vertex| vertex.clone()) + } +} + +impl AsRef<[G]> for NGon { + fn as_ref(&self) -> &[G] { + &self.0 } } -impl AsMut<[::Item]> for NGon +impl AsMut<[G]> for NGon { + fn as_mut(&mut self) -> &mut [G] { + &mut self.0 + } +} + +impl Adjunct for NGon { + type Item = G; +} + +impl Converged for NGon where - A: Array, + G: Copy, { - fn as_mut(&mut self) -> &mut [A::Item] { - self.0.as_mut_slice() + fn converged(vertex: G) -> Self { + NGon([vertex; N]) } } -impl Adjunct for NGon +impl DynamicArity for NGon where - A: Array, + Constant: ToType, + TypeOf: Cmp, { - type Item = A::Item; + type Dynamic = usize; + + fn arity(&self) -> Self::Dynamic { + ::ARITY + } } -impl Fold for NGon +impl Fold for NGon where - Self: Topological + IntoItems, - A: Array, + Self: Topological, { fn fold(self, mut seed: U, mut f: F) -> U where @@ -481,225 +524,168 @@ where } } -impl From for NGon -where - A: Array, -{ - fn from(array: A) -> Self { +impl From<[G; N]> for NGon { + fn from(array: [G; N]) -> Self { NGon(array) } } -impl FromItems for NGon -where - A: Array, -{ +impl FromItems for NGon { fn from_items(items: I) -> Option where I: IntoIterator, { - items - .into_iter() - .collect::>() - .into_inner() - .ok() - .map(NGon) + items.into_iter().try_collect().ok() } } -impl Index for NGon -where - A: Array + AsRef<[::Item]>, -{ - type Output = A::Item; +impl Index for NGon { + type Output = G; fn index(&self, index: usize) -> &Self::Output { self.0.as_ref().index(index) } } -impl IndexMut for NGon -where - A: Array + AsRef<[::Item]> + AsMut<[::Item]>, -{ +impl IndexMut for NGon { fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.0.as_mut().index_mut(index) } } -impl IntoItems for NGon -where - A: Array, -{ - type Output = ArrayVec; +impl IntoItems for NGon { + type Output = ::IntoIter; fn into_items(self) -> Self::Output { - ArrayVec::from(self.into_array()) + self.into_iter() } } -impl IntoIterator for NGon -where - A: Array, -{ - type Item = ::Item; - type IntoIter = <::Output as IntoIterator>::IntoIter; +impl IntoIterator for NGon { + type Item = G; + type IntoIter = array::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.into_items().into_iter() + array::IntoIter::new(self.into_array()) } } -macro_rules! impl_monomorphic_ngon { - (length => $n:expr) => ( - impl Monomorphic for NGon<[T; $n]> {} +impl Map for NGon { + type Output = NGon; + + fn map(self, f: F) -> Self::Output + where + F: FnMut(Self::Item) -> H, + { + (self.into_iter().map(f)).try_collect().unwrap() + } +} - impl StaticArity for NGon<[T; $n]> { - type Static = usize; +impl Monomorphic for NGon +where + Constant: ToType, + TypeOf: Cmp, +{ +} - const ARITY: Self::Static = $n; - } +impl Polygonal for NGon +where + // The compiler cannot deduce that the bounds on `TypeOf` are a strict + // subset of the similar bounds on the `Topological` implementation, so an + // explicit bound on `Self` is required. + Self: Topological, + Constant: ToType, + TypeOf: Cmp, +{ +} - impl Polygonal for NGon<[T; $n]> {} - ); - (lengths => $($n:expr),*$(,)?) => ( - impl Monomorphic for NGon<[T; 2]> {} +impl StaticArity for NGon +where + Constant: ToType, + TypeOf: Cmp, +{ + type Static = usize; - impl StaticArity for NGon<[T; 2]> { - type Static = usize; + const ARITY: Self::Static = crate::n_arity(N); +} - const ARITY: Self::Static = 1; - } +impl Topological for NGon +where + Constant: ToType, + TypeOf: Cmp, +{ + type Vertex = G; - $(impl_monomorphic_ngon!(length => $n);)* - ); + fn try_from_slice(vertices: I) -> Option + where + Self::Vertex: Copy, + I: AsRef<[Self::Vertex]>, + { + vertices.as_ref().try_into().map(NGon).ok() + } } -impl_monomorphic_ngon!(lengths => 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); -macro_rules! impl_zip_ngon { - (length => $n:expr) => ( - impl_zip_ngon!(length => $n, items => (A, B)); - impl_zip_ngon!(length => $n, items => (A, B, C)); - impl_zip_ngon!(length => $n, items => (A, B, C, D)); - impl_zip_ngon!(length => $n, items => (A, B, C, D, E)); - impl_zip_ngon!(length => $n, items => (A, B, C, D, E, F)); - ); - (length => $n:expr, items => ($($i:ident),*)) => ( - #[allow(non_snake_case)] - impl<$($i),*> Zip for ($(NGon<[$i; $n]>),*) { - type Output = NGon<[($($i),*); $n]>; +impl TryFromIterator for NGon { + type Error = (); - fn zip(self) -> Self::Output { - let ($($i,)*) = self; - FromItems::from_items(izip!($($i.into_items()),*)).unwrap() - } - } - ); + fn try_from_iter(vertices: I) -> Result + where + I: Iterator, + { + vertices + .collect::>() + .into_inner() + .map(NGon) + .map_err(|_| ()) + } } -// TODO: Some inherent functions are not documented to avoid bloat. -macro_rules! impl_ngon { - (length => $n:expr) => ( - impl NGon<[T; $n]> { - #[doc(hidden)] - pub fn positions(&self) -> NGon<[&Position; $n]> - where - T: AsPosition, - { - if let Ok(array) = self - .as_ref() - .iter() - .map(|vertex| vertex.as_position()) - .collect::>() - .into_inner() - { - array.into() - } - else { - panic!() - } - } - } +impl ZipMap for NGon { + type Output = NGon; - impl<'a, T> NGon<[&'a T; $n]> { - #[doc(hidden)] - pub fn cloned(self) -> NGon<[T; $n]> - where - T: Clone, - { - self.map(|vertex| vertex.clone()) - } - } + fn zip_map(self, other: Self, mut f: F) -> Self::Output + where + F: FnMut(Self::Item, Self::Item) -> H, + { + (self.into_iter().zip(other).map(|(a, b)| f(a, b))) + .try_collect() + .unwrap() + } +} - impl Converged for NGon<[T; $n]> +macro_rules! impl_zip_ngon { + (ngons => ($($i:ident),*)) => ( + #[allow(non_snake_case)] + impl<$($i),*, const N: usize> Zip for ($(NGon<$i, N>),*) where - T: Copy, + Constant: ToType, + TypeOf: Cmp, { - fn converged(item: T) -> Self { - NGon([item; $n]) - } - } - - impl DynamicArity for NGon<[T; $n]> { - type Dynamic = usize; - - fn arity(&self) -> Self::Dynamic { - ::ARITY - } - } - - impl Map for NGon<[T; $n]> { - type Output = NGon<[U; $n]>; + type Output = NGon<($($i),*), N>; - fn map(self, f: F) -> Self::Output - where - F: FnMut(Self::Item) -> U, - { - FromItems::from_items(self.into_iter().map(f)).unwrap() - } - } - - impl Topological for NGon<[T; $n]> { - type Vertex = T; - - fn try_from_slice(vertices: U) -> Option - where - Self::Vertex: Copy, - U: AsRef<[Self::Vertex]>, - { - vertices.as_ref().try_into().map(NGon).ok() - } - } - - impl_zip_ngon!(length => $n); - - impl ZipMap for NGon<[T; $n]> { - type Output = NGon<[U; $n]>; - - fn zip_map(self, other: Self, mut f: F) -> Self::Output - where - F: FnMut(Self::Item, Self::Item) -> U, - { - FromItems::from_items(self.into_iter().zip(other).map(|(a, b)| f(a, b))).unwrap() + fn zip(self) -> Self::Output { + let ($($i,)*) = self; + (izip!($($i.into_iter()),*)).try_collect().unwrap() } } ); - (lengths => $($n:expr),*$(,)?) => ( - $(impl_ngon!(length => $n);)* - ); } -impl_ngon!(lengths => 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); +impl_zip_ngon!(ngons => (A, B)); +impl_zip_ngon!(ngons => (A, B, C)); +impl_zip_ngon!(ngons => (A, B, C, D)); +impl_zip_ngon!(ngons => (A, B, C, D, E)); +impl_zip_ngon!(ngons => (A, B, C, D, E, F)); -pub type Edge = NGon<[T; 2]>; +pub type Edge = NGon; -impl Edge { - pub fn new(a: T, b: T) -> Self { +impl Edge { + pub fn new(a: G, b: G) -> Self { NGon([a, b]) } - pub fn line(&self) -> Option>> + pub fn line(&self) -> Option>> where - T: AsPosition, + G: AsPosition, { let [origin, endpoint] = self.positions().cloned().into_array(); Unit::try_from_inner(endpoint - origin).map(|direction| Line { origin, direction }) @@ -707,19 +693,21 @@ impl Edge { pub fn is_bisected(&self, other: &Self) -> bool where - T: AsPosition, - Position: FiniteDimensional, + G: AsPosition, + Position: FiniteDimensional, { - let is_disjoint = |line: Line>, [a, b]: [Position; 2]| { - fool::zip((line.partition(a), line.partition(b))) + let is_disjoint = |line: Line>, [a, b]: [Position; 2]| { + line.partition(a) + .zip(line.partition(b)) .map(|(pa, pb)| pa != pb) .unwrap_or(false) }; - fool::zip((self.line(), other.line())) - .map(|(l1, l2)| { - let l1 = is_disjoint(l1, other.positions().cloned().into_array()); - let l2 = is_disjoint(l2, self.positions().cloned().into_array()); - l1 && l2 + self.line() + .zip(other.line()) + .map(|(left, right)| { + let left = is_disjoint(left, other.positions().cloned().into_array()); + let right = is_disjoint(right, self.positions().cloned().into_array()); + left && right }) .unwrap_or(false) } @@ -778,13 +766,15 @@ where // edge's endpoints by the other edge's line. That's probably more // expensive than is necessary. fn intersection(&self, other: &Edge) -> Option { - fool::zip((self.line(), other.line())).and_then(|(l1, l2)| match l1.intersection(&l2) { - Some(LineLine::Point(point)) => self - .is_bisected(other) - .then_some_ext(EdgeEdge::Point(point)), - Some(LineLine::Line(_)) => todo!(), - _ => None, - }) + self.line() + .zip(other.line()) + .and_then(|(left, right)| match left.intersection(&right) { + Some(LineLine::Point(point)) => self + .is_bisected(other) + .then_some_ext(EdgeEdge::Point(point)), + Some(LineLine::Line(_)) => todo!(), + _ => None, + }) } } @@ -801,29 +791,29 @@ impl Rotate for Edge { } /// Triangle. -pub type Trigon = NGon<[T; 3]>; +pub type Trigon = NGon; -impl Trigon { - pub fn new(a: T, b: T, c: T) -> Self { +impl Trigon { + pub fn new(a: G, b: G, c: G) -> Self { NGon([a, b, c]) } #[allow(clippy::many_single_char_names)] - pub fn plane(&self) -> Option>> + pub fn plane(&self) -> Option>> where - T: AsPosition, - Position: EuclideanSpace + FiniteDimensional, - Vector>: Cross>>, + G: AsPosition, + Position: EuclideanSpace + FiniteDimensional, + Vector>: Cross>>, { let [a, b, c] = self.positions().cloned().into_array(); let v = a - b; let u = a - c; Unit::try_from_inner(v.cross(u)) - .map(move |normal| Plane::> { origin: a, normal }) + .map(move |normal| Plane::> { origin: a, normal }) } } -impl Rotate for Trigon { +impl Rotate for Trigon { fn rotate(self, n: isize) -> Self { let n = umod(n, Self::ARITY as isize); if n == 1 { @@ -841,15 +831,15 @@ impl Rotate for Trigon { } /// Quadrilateral. -pub type Tetragon = NGon<[T; 4]>; +pub type Tetragon = NGon; -impl Tetragon { - pub fn new(a: T, b: T, c: T, d: T) -> Self { +impl Tetragon { + pub fn new(a: G, b: G, c: G, d: G) -> Self { NGon([a, b, c, d]) } } -impl Rotate for Tetragon { +impl Rotate for Tetragon { #[allow(clippy::many_single_char_names)] fn rotate(self, n: isize) -> Self { let n = umod(n, Self::ARITY as isize); @@ -883,13 +873,13 @@ impl Rotate for Tetragon { /// [`primitive`]: crate::primitive /// [`NGon`]: crate::primitive::NGon #[derive(Clone, Copy, Debug, PartialEq)] -pub enum BoundedPolygon { - N3(Trigon), - N4(Tetragon), +pub enum BoundedPolygon { + N3(Trigon), + N4(Tetragon), } -impl AsRef<[T]> for BoundedPolygon { - fn as_ref(&self) -> &[T] { +impl AsRef<[G]> for BoundedPolygon { + fn as_ref(&self) -> &[G] { match *self { BoundedPolygon::N3(ref trigon) => trigon.as_ref(), BoundedPolygon::N4(ref tetragon) => tetragon.as_ref(), @@ -897,8 +887,8 @@ impl AsRef<[T]> for BoundedPolygon { } } -impl AsMut<[T]> for BoundedPolygon { - fn as_mut(&mut self) -> &mut [T] { +impl AsMut<[G]> for BoundedPolygon { + fn as_mut(&mut self) -> &mut [G] { match *self { BoundedPolygon::N3(ref mut trigon) => trigon.as_mut(), BoundedPolygon::N4(ref mut tetragon) => tetragon.as_mut(), @@ -906,22 +896,22 @@ impl AsMut<[T]> for BoundedPolygon { } } -impl Adjunct for BoundedPolygon { - type Item = T; +impl Adjunct for BoundedPolygon { + type Item = G; } -impl DynamicArity for BoundedPolygon { +impl DynamicArity for BoundedPolygon { type Dynamic = usize; fn arity(&self) -> Self::Dynamic { match *self { - BoundedPolygon::N3(..) => Trigon::::ARITY, - BoundedPolygon::N4(..) => Tetragon::::ARITY, + BoundedPolygon::N3(..) => Trigon::::ARITY, + BoundedPolygon::N4(..) => Tetragon::::ARITY, } } } -impl Fold for BoundedPolygon { +impl Fold for BoundedPolygon { fn fold(self, mut seed: U, mut f: F) -> U where F: FnMut(U, Self::Item) -> U, @@ -933,32 +923,32 @@ impl Fold for BoundedPolygon { } } -impl From<[T; 3]> for BoundedPolygon { - fn from(array: [T; 3]) -> Self { +impl From<[G; 3]> for BoundedPolygon { + fn from(array: [G; 3]) -> Self { BoundedPolygon::N3(array.into()) } } -impl From<[T; 4]> for BoundedPolygon { - fn from(array: [T; 4]) -> Self { +impl From<[G; 4]> for BoundedPolygon { + fn from(array: [G; 4]) -> Self { BoundedPolygon::N4(array.into()) } } -impl From> for BoundedPolygon { - fn from(trigon: Trigon) -> Self { +impl From> for BoundedPolygon { + fn from(trigon: Trigon) -> Self { BoundedPolygon::N3(trigon) } } -impl From> for BoundedPolygon { - fn from(tetragon: Tetragon) -> Self { +impl From> for BoundedPolygon { + fn from(tetragon: Tetragon) -> Self { BoundedPolygon::N4(tetragon) } } -impl Index for BoundedPolygon { - type Output = T; +impl Index for BoundedPolygon { + type Output = G; fn index(&self, index: usize) -> &Self::Output { match *self { @@ -968,7 +958,7 @@ impl Index for BoundedPolygon { } } -impl IndexMut for BoundedPolygon { +impl IndexMut for BoundedPolygon { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match *self { BoundedPolygon::N3(ref mut trigon) => trigon.index_mut(index), @@ -977,8 +967,8 @@ impl IndexMut for BoundedPolygon { } } -impl IntoItems for BoundedPolygon { - type Output = SmallVec<[T; 4]>; +impl IntoItems for BoundedPolygon { + type Output = SmallVec<[G; 4]>; fn into_items(self) -> Self::Output { match self { @@ -988,8 +978,8 @@ impl IntoItems for BoundedPolygon { } } -impl IntoIterator for BoundedPolygon { - type Item = T; +impl IntoIterator for BoundedPolygon { + type Item = G; type IntoIter = <::Output as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -1011,9 +1001,9 @@ impl Map for BoundedPolygon { } } -impl Polygonal for BoundedPolygon {} +impl Polygonal for BoundedPolygon {} -impl Rotate for BoundedPolygon { +impl Rotate for BoundedPolygon { fn rotate(self, n: isize) -> Self { match self { BoundedPolygon::N3(trigon) => BoundedPolygon::N3(trigon.rotate(n)), @@ -1022,19 +1012,19 @@ impl Rotate for BoundedPolygon { } } -impl StaticArity for BoundedPolygon { +impl StaticArity for BoundedPolygon { type Static = (usize, usize); const ARITY: Self::Static = (3, 4); } -impl Topological for BoundedPolygon { - type Vertex = T; +impl Topological for BoundedPolygon { + type Vertex = G; - fn try_from_slice(vertices: U) -> Option + fn try_from_slice(vertices: I) -> Option where Self::Vertex: Copy, - U: AsRef<[Self::Vertex]>, + I: AsRef<[Self::Vertex]>, { let vertices = vertices.as_ref(); match vertices.len() { @@ -1059,51 +1049,51 @@ impl Topological for BoundedPolygon { /// [`BoundedPolygon`]: crate::primitive::BoundedPolygon /// [`NGon`]: crate::primitive::NGon #[derive(Clone, Debug, PartialEq)] -pub struct UnboundedPolygon(SmallVec<[T; 4]>); +pub struct UnboundedPolygon(SmallVec<[G; 4]>); -impl UnboundedPolygon { - pub fn trigon(a: T, b: T, c: T) -> Self { +impl UnboundedPolygon { + pub fn trigon(a: G, b: G, c: G) -> Self { UnboundedPolygon(smallvec![a, b, c]) } - pub fn tetragon(a: T, b: T, c: T, d: T) -> Self { + pub fn tetragon(a: G, b: G, c: G, d: G) -> Self { UnboundedPolygon(smallvec![a, b, c, d]) } - pub fn positions(&self) -> UnboundedPolygon<&Position> + pub fn positions(&self) -> UnboundedPolygon<&Position> where - T: AsPosition, + G: AsPosition, { UnboundedPolygon(self.0.iter().map(|vertex| vertex.as_position()).collect()) } } -impl<'a, T> UnboundedPolygon<&'a T> +impl<'a, G> UnboundedPolygon<&'a G> where - T: Clone, + G: Clone, { - pub fn cloned(self) -> UnboundedPolygon { + pub fn cloned(self) -> UnboundedPolygon { self.map(|vertex| vertex.clone()) } } -impl Adjunct for UnboundedPolygon { - type Item = T; +impl Adjunct for UnboundedPolygon { + type Item = G; } -impl AsRef<[T]> for UnboundedPolygon { - fn as_ref(&self) -> &[T] { +impl AsRef<[G]> for UnboundedPolygon { + fn as_ref(&self) -> &[G] { self.0.as_ref() } } -impl AsMut<[T]> for UnboundedPolygon { - fn as_mut(&mut self) -> &mut [T] { +impl AsMut<[G]> for UnboundedPolygon { + fn as_mut(&mut self) -> &mut [G] { self.0.as_mut() } } -impl DynamicArity for UnboundedPolygon { +impl DynamicArity for UnboundedPolygon { type Dynamic = usize; fn arity(&self) -> Self::Dynamic { @@ -1111,52 +1101,60 @@ impl DynamicArity for UnboundedPolygon { } } -impl From> for UnboundedPolygon +impl From> for UnboundedPolygon where - T: Clone, + G: Clone, { - fn from(polygon: BoundedPolygon) -> Self { + fn from(polygon: BoundedPolygon) -> Self { UnboundedPolygon(SmallVec::from(polygon.as_ref())) } } -impl FromItems for UnboundedPolygon { +impl From> for UnboundedPolygon +where + Constant: ToType, + TypeOf: Cmp, + G: Clone, +{ + fn from(ngon: NGon) -> Self { + UnboundedPolygon(SmallVec::from(ngon.as_ref())) + } +} + +impl FromItems for UnboundedPolygon { fn from_items(items: I) -> Option where I: IntoIterator, { - items - .into_iter() - .has_at_least(3) - .map(|items| UnboundedPolygon(items.collect())) + items.into_iter().try_collect().ok() } } -impl Index for UnboundedPolygon { - type Output = T; +impl Index for UnboundedPolygon { + type Output = G; fn index(&self, index: usize) -> &Self::Output { self.0.index(index) } } -impl IndexMut for UnboundedPolygon { +impl IndexMut for UnboundedPolygon { fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.0.index_mut(index) } } -impl IntoItems for UnboundedPolygon { - type Output = SmallVec<[T; 4]>; +impl IntoItems for UnboundedPolygon { + type Output = SmallVec<[G; 4]>; fn into_items(self) -> Self::Output { self.0 } } -impl IntoIterator for UnboundedPolygon { - type IntoIter = as IntoIterator>::IntoIter; - type Item = T; +impl IntoIterator for UnboundedPolygon { + type IntoIter = as IntoIterator>::IntoIter; + type Item = G; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() @@ -1174,21 +1172,21 @@ impl Map for UnboundedPolygon { } } -impl Polygonal for UnboundedPolygon {} +impl Polygonal for UnboundedPolygon {} -impl StaticArity for UnboundedPolygon { +impl StaticArity for UnboundedPolygon { type Static = (usize, Option); const ARITY: Self::Static = (3, None); } -impl Topological for UnboundedPolygon { - type Vertex = T; +impl Topological for UnboundedPolygon { + type Vertex = G; - fn try_from_slice(vertices: U) -> Option + fn try_from_slice(vertices: I) -> Option where Self::Vertex: Copy, - U: AsRef<[Self::Vertex]>, + I: AsRef<[Self::Vertex]>, { let vertices = vertices.as_ref(); if vertices.len() > 2 { @@ -1200,22 +1198,19 @@ impl Topological for UnboundedPolygon { } } -macro_rules! impl_unbounded_polygon { - (length => $n:expr) => ( - impl From> for UnboundedPolygon - where - T: Clone, - { - fn from(ngon: NGon<[T; $n]>) -> Self { - UnboundedPolygon(SmallVec::from(ngon.as_ref())) - } - } - ); - (lengths => $($n:expr),*$(,)?) => ( - $(impl_unbounded_polygon!(length => $n);)* - ); +impl TryFromIterator for UnboundedPolygon { + type Error = (); + + fn try_from_iter(vertices: I) -> Result + where + I: Iterator, + { + vertices + .has_at_least(3) + .map(|items| UnboundedPolygon(items.collect())) + .ok_or(()) + } } -impl_unbounded_polygon!(lengths => 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); /// Zips the vertices of [`Topological`] types from multiple iterators into a /// single iterator. @@ -1281,7 +1276,7 @@ where /// the polygon. /// /// [`Polygonal`]: crate::primitive::Polygonal -fn angles<'a, P>(polygon: &'a P) -> impl 'a + Clone + Iterator>> +fn angles