From 3d18f0d184e1cfa5105d71048d9388f8b130bcd9 Mon Sep 17 00:00:00 2001 From: Sean Olson Date: Thu, 25 Mar 2021 18:17:39 -0700 Subject: [PATCH 1/2] Implement `NGon` and `Flat` with constant generics. This change introduces constant generics and uses them to implement the `NGon` and `Flat` APIs. As part of this change, the dependency on the `arrayvec` crate has been upgraded and the `Array` trait is no longer used. The minimum supported Rust version has been bumped to 1.51.0 for these changes. Because constraints and computations on constant generics are not yet supported, traits for morphisms between `typenum` types and `usize` constant have also be introduced in the `constant` module. This allows for relations and computations on constants in APIs. Additionally, this change improves some documentation and prefers a new `TryFromIterator` trait over the dubious `FromItems` and `IntoItems` traits from the `theon` crate. --- .github/workflows/continuous-integration.yml | 4 +- pictor/src/pipeline.rs | 23 +- plexus/Cargo.toml | 2 +- plexus/src/buffer/builder.rs | 21 +- plexus/src/buffer/mod.rs | 84 +-- plexus/src/constant.rs | 44 ++ plexus/src/graph/edge.rs | 52 +- plexus/src/graph/face.rs | 20 +- plexus/src/graph/geometry.rs | 29 +- plexus/src/graph/mod.rs | 59 +- plexus/src/graph/vertex.rs | 20 +- plexus/src/index.rs | 81 +-- plexus/src/lib.rs | 128 +++- plexus/src/primitive/cube.rs | 36 +- plexus/src/primitive/decompose.rs | 84 ++- plexus/src/primitive/mod.rs | 627 +++++++++---------- plexus/src/primitive/sphere.rs | 12 +- 17 files changed, 743 insertions(+), 583 deletions(-) create mode 100644 plexus/src/constant.rs 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..dc4e5dd7 100644 --- a/plexus/Cargo.toml +++ b/plexus/Cargo.toml @@ -50,7 +50,7 @@ unstable = [] [dependencies] approx = "^0.3.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() ) } } From b5812aa34af4d9844a56d81f8d9b03529919f780 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 May 2021 21:29:29 +0000 Subject: [PATCH 2/2] Update approx requirement from ^0.3.0 to ^0.4.0 Updates the requirements on [approx](https://github.com/brendanzab/approx) to permit the latest version. - [Release notes](https://github.com/brendanzab/approx/releases) - [Commits](https://github.com/brendanzab/approx/commits) Signed-off-by: dependabot[bot] --- plexus/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexus/Cargo.toml b/plexus/Cargo.toml index dc4e5dd7..b91dddd2 100644 --- a/plexus/Cargo.toml +++ b/plexus/Cargo.toml @@ -48,7 +48,7 @@ geometry-ultraviolet = ["theon/geometry-ultraviolet"] unstable = [] [dependencies] -approx = "^0.3.0" +approx = "^0.4.0" ahash = "^0.6.0" arrayvec = "^0.6.0" decorum = "^0.3.1"

@@ -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