├── .github ├── dependabot.yml └── workflows │ └── continuous-integration.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── subdivide.rs ├── data ├── cube.ply ├── plexus.ply └── teapot.ply ├── doc ├── katex-header.html ├── plexus-favicon.ico ├── plexus-favicon.svg ├── plexus-minimal.svg └── plexus.svg ├── examples ├── sphere │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── subdivide │ ├── Cargo.toml │ └── src │ │ └── main.rs └── teapot │ ├── Cargo.toml │ └── src │ └── main.rs ├── pictor ├── Cargo.toml └── src │ ├── camera.rs │ ├── harness.rs │ ├── lib.rs │ ├── pipeline.rs │ ├── renderer.rs │ ├── shader.glsl.frag │ ├── shader.glsl.vert │ ├── shader.spv.frag │ └── shader.spv.vert ├── plexus ├── Cargo.toml └── src │ ├── buffer │ ├── builder.rs │ └── mod.rs │ ├── builder.rs │ ├── constant.rs │ ├── encoding │ ├── mod.rs │ └── ply.rs │ ├── entity │ ├── borrow.rs │ ├── dijkstra.rs │ ├── mod.rs │ ├── storage │ │ ├── hash.rs │ │ └── mod.rs │ ├── traverse.rs │ └── view.rs │ ├── geometry │ ├── mod.rs │ └── partition.rs │ ├── graph │ ├── builder.rs │ ├── core.rs │ ├── data.rs │ ├── edge.rs │ ├── face.rs │ ├── geometry.rs │ ├── mod.rs │ ├── mutation │ │ ├── edge.rs │ │ ├── face.rs │ │ ├── mod.rs │ │ ├── path.rs │ │ └── vertex.rs │ ├── path.rs │ └── vertex.rs │ ├── index.rs │ ├── integration │ ├── cgmath.rs │ ├── glam.rs │ ├── mint.rs │ ├── mod.rs │ ├── nalgebra.rs │ └── ultraviolet.rs │ ├── lib.rs │ ├── primitive │ ├── cube.rs │ ├── decompose.rs │ ├── generate.rs │ ├── mod.rs │ └── sphere.rs │ └── transact.rs ├── rustdoc.sh └── rustfmt.toml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [pull_request, push] 3 | jobs: 4 | rustfmt: 5 | name: Format 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions-rs/toolchain@v1 10 | with: 11 | profile: minimal 12 | toolchain: nightly 13 | override: true 14 | components: rustfmt 15 | - uses: actions-rs/cargo@v1 16 | with: 17 | command: fmt 18 | args: --all -- --check 19 | clippy: 20 | name: Lint 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | components: clippy 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: clippy 33 | args: --all-features --all-targets -- -D clippy::all 34 | test: 35 | name: Test 36 | needs: [clippy, rustfmt] 37 | runs-on: ${{ matrix.os }} 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | os: [macOS-latest, ubuntu-latest, windows-latest] 42 | toolchain: 43 | - 1.81.0 # Minimum. 44 | - stable 45 | - beta 46 | - nightly 47 | steps: 48 | - uses: actions/checkout@v2 49 | - uses: actions-rs/toolchain@v1 50 | with: 51 | profile: minimal 52 | toolchain: ${{ matrix.toolchain }} 53 | override: true 54 | - uses: actions-rs/cargo@v1 55 | with: 56 | command: test 57 | args: --manifest-path=plexus/Cargo.toml --features geometry-nalgebra --no-default-features --verbose 58 | - uses: actions-rs/cargo@v1 59 | with: 60 | command: test 61 | args: --manifest-path=plexus/Cargo.toml --all-features --verbose 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | **/*.rs.bk 4 | *.iml 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "examples/sphere", 4 | "examples/subdivide", 5 | "examples/teapot", 6 | "pictor", 7 | "plexus", 8 | ] 9 | resolver = "2" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Plexus 3 |
4 |
5 | 6 | **Plexus** is a highly composable Rust library for polygonal mesh processing. 7 | See [the website][website] for the most recent [API documentation][rustdoc] and 8 | the [user guide][guide]. 9 | 10 | [![GitHub](https://img.shields.io/badge/GitHub-olson--sean--k/plexus-8da0cb?logo=github&style=for-the-badge)](https://github.com/olson-sean-k/plexus) 11 | [![docs.rs](https://img.shields.io/badge/docs.rs-plexus-66c2a5?logo=rust&style=for-the-badge)](https://docs.rs/plexus) 12 | [![crates.io](https://img.shields.io/crates/v/plexus.svg?logo=rust&style=for-the-badge)](https://crates.io/crates/plexus) 13 | [![Gitter](https://img.shields.io/badge/Gitter-plexus--rs-c266a5?logo=gitter&style=for-the-badge)](https://gitter.im/plexus-rs/community) 14 | 15 | ## Primitives 16 | 17 | Plexus provides primitive topological structures that can be composed using 18 | generators and iterator expressions. Iterator expressions operate over a 19 | sequence of polygons with arbitrary vertex data. These polygons can be 20 | decomposed, tessellated, indexed, and collected into mesh data structures. 21 | 22 | ```rust 23 | use decorum::R64; // See "Integrations". 24 | use nalgebra::Point3; 25 | use plexus::buffer::MeshBuffer; 26 | use plexus::index::Flat3; 27 | use plexus::prelude::*; 28 | use plexus::primitive::generate::Position; 29 | use plexus::primitive::sphere::UvSphere; 30 | 31 | use crate::render::pipeline::{Color4, Vertex}; 32 | 33 | type E3 = Point3; 34 | 35 | // Create a buffer of index and vertex data from a uv-sphere. 36 | let buffer: MeshBuffer = UvSphere::new(16, 8) 37 | .polygons::>() 38 | .map_vertices(|position| Vertex::new(position, Color4::white())) 39 | .triangulate() 40 | .collect(); 41 | ``` 42 | 43 | The above example uses a generator and iterator expression to transform the 44 | positional data of a sphere into a linear buffer for indexed drawing. See [the 45 | sphere example][example-sphere] for a rendered demonstration. 46 | 47 | ## Half-Edge Graphs 48 | 49 | The `MeshGraph` type represents polygonal meshes as an ergonomic [half-edge 50 | graph][dcel] that supports arbitrary data in vertices, arcs (half-edges), edges, 51 | and faces. Graphs can be traversed and manipulated in many ways that iterator 52 | expressions and linear buffers cannot. 53 | 54 | ```rust 55 | use plexus::graph::MeshGraph; 56 | use plexus::prelude::*; 57 | use plexus::primitive::Tetragon; 58 | use ultraviolet::vec::Vec3; 59 | 60 | type E3 = Vec3; 61 | 62 | // Create a graph of a tetragon. 63 | let mut graph = MeshGraph::::from(Tetragon::from([ 64 | (1.0, 1.0, 0.0), 65 | (-1.0, 1.0, 0.0), 66 | (-1.0, -1.0, 0.0), 67 | (1.0, -1.0, 0.0), 68 | ])); 69 | // Extrude the face of the tetragon. 70 | let key = graph.faces().next().unwrap().key(); 71 | let face = graph.face_mut(key).unwrap().extrude_with_offset(1.0).unwrap(); 72 | ``` 73 | 74 | Plexus avoids exposing very basic topological operations like inserting 75 | individual vertices into a graph, because they can easily be done incorrectly. 76 | Instead, graphs are typically manipulated with more abstract operations like 77 | merging and splitting. 78 | 79 | See [the user guide][guide-graphs] for more details about graphs. 80 | 81 | ## Geometric Traits 82 | 83 | Plexus provides optional traits to support spatial operations by exposing 84 | positional data in vertices. If the data exposed by the `AsPosition` trait 85 | supports these geometric traits, then geometric operations become available in 86 | primitive and mesh data structure APIs. 87 | 88 | ```rust 89 | use glam::Vec3A; 90 | use plexus::geometry::{AsPosition, Vector}; 91 | use plexus::graph::GraphData; 92 | use plexus::prelude::*; 93 | 94 | type E3 = Vec3A; 95 | 96 | #[derive(Clone, Copy, PartialEq)] 97 | pub struct Vertex { 98 | pub position: E3, 99 | pub normal: Vector, 100 | } 101 | 102 | impl GraphData for Vertex { 103 | type Vertex = Self; 104 | type Arc = (); 105 | type Edge = (); 106 | type Face = (); 107 | } 108 | 109 | impl AsPosition for Vertex { 110 | type Position = E3; 111 | 112 | fn as_position(&self) -> &Self::Position { 113 | &self.position 114 | } 115 | } 116 | ``` 117 | 118 | Data structures like `MeshGraph` also provide functions that allow user code to 119 | compute geometry without requiring any of these traits; the data in these 120 | structures may be arbitrary, including no data at all. 121 | 122 | ## Integrations 123 | 124 | Plexus integrates with the [`theon`] crate to provide geometric traits and 125 | support various mathematics crates in the Rust ecosystem. Any mathematics crate 126 | can be used and, if it is supported by Theon, Plexus provides geometric APIs by 127 | enabling Cargo features. 128 | 129 | | Feature | Default | Crate | 130 | |------------------------|---------|-----------------| 131 | | `geometry-cgmath` | No | [`cgmath`] | 132 | | `geometry-glam` | No | [`glam`] | 133 | | `geometry-mint` | No | [`mint`] | 134 | | `geometry-nalgebra` | No | [`nalgebra`] | 135 | | `geometry-ultraviolet` | No | [`ultraviolet`] | 136 | 137 | Enabling the corresponding feature is recommended if using one of these 138 | supported crates. 139 | 140 | Plexus also integrates with the [`decorum`] crate for floating-point 141 | representations that can be hashed for fast indexing. The `R64` type is a 142 | (totally ordered) real number with an `f64` representation that cannot be `NaN` 143 | nor infinity, for example. Geometric conversion traits are implemented for 144 | supported types to allow for implicit conversions of scalar types. 145 | 146 | ## Encodings 147 | 148 | Plexus provides support for polygonal mesh encodings. This allows mesh data 149 | structures like `MeshGraph` and `MeshBuffer` to be serialized and deserialized 150 | to and from various formats. 151 | 152 | ```rust 153 | use nalgebra::Point3; 154 | use plexus::encoding::ply::{FromPly, PositionEncoding}; 155 | use plexus::graph::MeshGraph; 156 | use plexus::prelude::*; 157 | use std::fs::File; 158 | 159 | type E3 = Point3; 160 | 161 | let ply = File::open("cube.ply").unwrap(); 162 | let encoding = PositionEncoding::::default(); 163 | let (graph, _) = MeshGraph::::from_ply(encoding, ply).unwrap(); 164 | ``` 165 | 166 | Encoding support is optional and enabled via Cargo features. 167 | 168 | | Feature | Default | Encoding | Read | Write | 169 | |----------------|---------|----------|------|-------| 170 | | `encoding-ply` | No | PLY | Yes | No | 171 | 172 | See [the teapot example][example-teapot] for a rendered demonstration of reading 173 | a mesh from the file system. 174 | 175 | [dcel]: https://en.wikipedia.org/wiki/doubly_connected_edge_list 176 | 177 | [guide]: https://plexus.rs/user-guide/getting-started 178 | [guide-graphs]: https://plexus.rs/user-guide/graphs 179 | [rustdoc]: https://plexus.rs/rustdoc/plexus 180 | [website]: https://plexus.rs 181 | 182 | [example-sphere]: https://github.com/olson-sean-k/plexus/tree/master/examples/sphere/src/main.rs 183 | [example-teapot]: https://github.com/olson-sean-k/plexus/tree/master/examples/teapot/src/main.rs 184 | 185 | [`cgmath`]: https://crates.io/crates/cgmath 186 | [`decorum`]: https://crates.io/crates/decorum 187 | [`glam`]: https://crates.io/crates/glam 188 | [`mint`]: https://crates.io/crates/mint 189 | [`nalgebra`]: https://crates.io/crates/nalgebra 190 | [`theon`]: https://crates.io/crates/theon 191 | [`ultraviolet`]: https://crates.io/crates/ultraviolet 192 | -------------------------------------------------------------------------------- /benches/subdivide.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; 2 | use nalgebra::Point3; 3 | use plexus::geometry::AsPositionMut; 4 | use plexus::graph::{EdgeMidpoint, FaceView, GraphData, MeshGraph}; 5 | use plexus::prelude::*; 6 | use plexus::primitive::Tetragon; 7 | use smallvec::SmallVec; 8 | 9 | const DEPTH: usize = 8; 10 | 11 | type E3 = Point3; 12 | 13 | pub trait Ambo { 14 | #[must_use] 15 | fn ambo(self) -> Self; 16 | } 17 | 18 | impl Ambo for FaceView<&'_ mut MeshGraph> 19 | where 20 | G: EdgeMidpoint + GraphData, 21 | G::Vertex: AsPositionMut, 22 | { 23 | // Subdivide the face with a polygon formed from vertices at the midpoints 24 | // of the edges of the face. 25 | fn ambo(self) -> Self { 26 | // Split each edge, stashing the vertex key and moving to the next arc. 27 | let arity = self.arity(); 28 | let mut arc = self.into_arc(); 29 | let mut splits = SmallVec::<[_; 4]>::with_capacity(arity); 30 | for _ in 0..arity { 31 | let vertex = arc.split_at_midpoint(); 32 | splits.push(vertex.key()); 33 | arc = vertex.into_outgoing_arc().into_next_arc(); 34 | } 35 | // Split faces along the vertices from each arc split. 36 | let mut face = arc.into_face().unwrap(); 37 | for (a, b) in splits.into_iter().perimeter() { 38 | face = face.split(a, b).unwrap().into_face().unwrap(); 39 | } 40 | // Return the face forming the similar polygon. 41 | face 42 | } 43 | } 44 | 45 | fn tetragon() -> MeshGraph { 46 | // Create a graph from a tetragon. 47 | MeshGraph::from(Tetragon::from([ 48 | (1.0, 0.0, -1.0), 49 | (-1.0, 0.0, -1.0), 50 | (-1.0, 0.0, 1.0), 51 | (1.0, 0.0, 1.0), 52 | ])) 53 | } 54 | 55 | fn subdivide(mut graph: MeshGraph) { 56 | // Get the face of the tetragon. 57 | let key = graph.faces().next().unwrap().key(); 58 | let mut face = graph.face_mut(key).unwrap(); 59 | 60 | // Subdivide and extrude the face repeatedly. 61 | for _ in 0..DEPTH { 62 | face = face.ambo().extrude_with_offset(0.5).unwrap(); 63 | } 64 | } 65 | 66 | fn benchmark(criterion: &mut Criterion) { 67 | criterion.bench_function("subdivide", move |bencher| { 68 | bencher.iter_batched(tetragon, subdivide, BatchSize::SmallInput) 69 | }); 70 | } 71 | 72 | criterion_group!(benches, benchmark); 73 | criterion_main!(benches); 74 | -------------------------------------------------------------------------------- /data/cube.ply: -------------------------------------------------------------------------------- 1 | ply 2 | format ascii 1.0 3 | element vertex 8 4 | property float x 5 | property float y 6 | property float z 7 | element face 6 8 | property list uint8 uint32 vertex_index 9 | end_header 10 | 0 0 0 11 | 0 0 1 12 | 0 1 1 13 | 0 1 0 14 | 1 0 0 15 | 1 0 1 16 | 1 1 1 17 | 1 1 0 18 | 4 0 1 2 3 19 | 4 7 6 5 4 20 | 4 0 4 5 1 21 | 4 1 5 6 2 22 | 4 2 6 7 3 23 | 4 3 7 4 0 24 | -------------------------------------------------------------------------------- /data/plexus.ply: -------------------------------------------------------------------------------- 1 | ply 2 | format ascii 1.0 3 | comment 0 1 3 4 5 10 12 14 20 21 27 28 35 36 43 44 53 54 4 | element vertex 55 5 | property float x 6 | property float y 7 | property float z 8 | element face 56 9 | property list uint8 uint32 vertex_index 10 | end_header 11 | 0 1 0 12 | -2 0 0 13 | 0 0 0 14 | 2 0 0 15 | 2 -1 0 16 | 0 -2 0 17 | -2 -1 0 18 | -2 -3 0 19 | 0 -4 0 20 | -2 -5 0 21 | 0 -6 0 22 | -2 -7 0 23 | 0 -8 0 24 | -2 -9 0 25 | 0 -10 0 26 | 2 -7 0 27 | 4 -6 0 28 | 2 -5 0 29 | 6 -5 0 30 | 4 -4 0 31 | 8 -4 0 32 | 6 -3 0 33 | 8 -2 0 34 | 6 -1 0 35 | 8 0 0 36 | 6 1 0 37 | 8 2 0 38 | 6 2 0 39 | 8 3 0 40 | 6 3 0 41 | 6 4 0 42 | 4 3 0 43 | 4 5 0 44 | 2 4 0 45 | 2 6 0 46 | 0 7 0 47 | 0 5 0 48 | -2 6 0 49 | -2 4 0 50 | -4 5 0 51 | -4 3 0 52 | -6 4 0 53 | -6 3 0 54 | -6 2 0 55 | -8 3 0 56 | -6 1 0 57 | -8 2 0 58 | -6 -1 0 59 | -8 0 0 60 | -6 -3 0 61 | -8 -2 0 62 | -6 -5 0 63 | -8 -4 0 64 | -6 -7 0 65 | -8 -6 0 66 | 3 4 2 5 67 | 3 3 2 4 68 | 3 3 0 2 69 | 3 0 1 2 70 | 3 2 1 6 71 | 3 2 6 5 72 | 3 5 6 7 73 | 3 5 7 8 74 | 3 8 7 9 75 | 3 8 9 10 76 | 3 10 9 11 77 | 3 10 11 12 78 | 3 12 11 13 79 | 3 12 13 14 80 | 3 15 10 12 81 | 3 17 10 15 82 | 3 16 17 15 83 | 3 19 17 16 84 | 3 18 19 16 85 | 3 21 19 18 86 | 3 20 21 18 87 | 3 22 21 20 88 | 3 22 23 21 89 | 3 24 23 22 90 | 3 24 25 23 91 | 3 26 25 24 92 | 3 26 27 25 93 | 3 26 29 27 94 | 3 28 29 26 95 | 3 28 30 29 96 | 3 29 31 27 97 | 3 30 31 29 98 | 3 30 32 31 99 | 3 32 33 31 100 | 3 32 34 33 101 | 3 34 36 33 102 | 3 34 35 36 103 | 3 35 37 36 104 | 3 36 37 38 105 | 3 37 39 38 106 | 3 38 39 40 107 | 3 39 41 40 108 | 3 40 41 42 109 | 3 40 42 43 110 | 3 41 44 42 111 | 3 42 44 46 112 | 3 42 46 43 113 | 3 43 46 45 114 | 3 45 46 48 115 | 3 45 48 47 116 | 3 47 48 50 117 | 3 47 50 49 118 | 3 49 50 52 119 | 3 49 52 51 120 | 3 51 52 54 121 | 3 51 54 53 122 | -------------------------------------------------------------------------------- /doc/katex-header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | -------------------------------------------------------------------------------- /doc/plexus-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olson-sean-k/plexus/b4e4a4b0fb0948101f2b15ca164027053123198b/doc/plexus-favicon.ico -------------------------------------------------------------------------------- /doc/plexus-favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /doc/plexus-minimal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /doc/plexus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | image/svg+xml 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/sphere/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-sphere" 3 | version = "0.0.0" 4 | edition = "2021" 5 | authors = ["Sean Olson "] 6 | license = "MIT" 7 | publish = false 8 | 9 | [dependencies] 10 | nalgebra = "^0.33.0" 11 | theon = "^0.1.0" 12 | 13 | [dependencies.pictor] 14 | path = "../../pictor" 15 | 16 | [dependencies.plexus] 17 | path = "../../plexus" 18 | default-features = false 19 | features = [ 20 | "encoding-ply", 21 | "geometry-nalgebra", 22 | ] 23 | -------------------------------------------------------------------------------- /examples/sphere/src/main.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Point3; 2 | use pictor::pipeline::{self, Vertex}; 3 | use plexus::prelude::*; 4 | use plexus::primitive; 5 | use plexus::primitive::generate::{Normal, Position}; 6 | use plexus::primitive::sphere::UvSphere; 7 | use theon::space::{EuclideanSpace, VectorSpace}; 8 | 9 | type E3 = Point3; 10 | 11 | fn main() { 12 | let from = Point3::new(0.0, 0.0, 3.0); 13 | let to = Point3::origin(); 14 | pipeline::render_mesh_buffer_with(from, to, move || { 15 | let sphere = UvSphere::new(32, 16); 16 | primitive::zip_vertices(( 17 | sphere.polygons::>().triangulate(), 18 | sphere.polygons::>().triangulate(), 19 | )) 20 | .map_vertices(|(position, normal)| Vertex { 21 | position: position.into_homogeneous().into(), 22 | normal: normal.into_inner().into_homogeneous().into(), 23 | color: [1.0, 0.6, 0.2, 1.0], 24 | }) 25 | .collect() 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /examples/subdivide/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-subdivide" 3 | version = "0.0.0" 4 | edition = "2021" 5 | authors = ["Sean Olson "] 6 | license = "MIT" 7 | publish = false 8 | 9 | [dependencies] 10 | nalgebra = "^0.33.0" 11 | smallvec = "^1.0.0" 12 | theon = "^0.1.0" 13 | 14 | [dependencies.pictor] 15 | path = "../../pictor" 16 | 17 | [dependencies.plexus] 18 | path = "../../plexus" 19 | default-features = false 20 | features = ["geometry-nalgebra"] 21 | -------------------------------------------------------------------------------- /examples/subdivide/src/main.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Point3; 2 | use pictor::pipeline::{self, Vertex}; 3 | use plexus::geometry::AsPositionMut; 4 | use plexus::graph::{ClosedView, EdgeMidpoint, FaceView, GraphData, MeshGraph}; 5 | use plexus::prelude::*; 6 | use plexus::primitive::Tetragon; 7 | use smallvec::SmallVec; 8 | use theon::space::{EuclideanSpace, VectorSpace}; 9 | 10 | type E3 = Point3; 11 | 12 | pub trait Ambo: ClosedView { 13 | #[must_use] 14 | fn ambo(self) -> Self; 15 | } 16 | 17 | impl Ambo for FaceView<&'_ mut MeshGraph> 18 | where 19 | G: EdgeMidpoint + GraphData, 20 | G::Vertex: AsPositionMut, 21 | { 22 | // Subdivide the face with a polygon formed from vertices at the midpoints 23 | // of the edges of the face. 24 | fn ambo(self) -> Self { 25 | // Split each edge, stashing the vertex key and moving to the next arc. 26 | let arity = self.arity(); 27 | let mut arc = self.into_arc(); 28 | let mut splits = SmallVec::<[_; 4]>::with_capacity(arity); 29 | for _ in 0..arity { 30 | let vertex = arc.split_at_midpoint(); 31 | splits.push(vertex.key()); 32 | arc = vertex.into_outgoing_arc().into_next_arc(); 33 | } 34 | // Split faces along the vertices from each arc split. 35 | let mut face = arc.into_face().unwrap(); 36 | for (a, b) in splits.into_iter().perimeter() { 37 | face = face.split(a, b).unwrap().into_face().unwrap(); 38 | } 39 | // Return the face forming the similar polygon. 40 | face 41 | } 42 | } 43 | 44 | fn main() { 45 | let from = Point3::new(-0.9, 3.1, 2.4); 46 | let to = Point3::new(0.0, 1.0, 0.0); 47 | pipeline::render_mesh_buffer_with(from, to, || { 48 | // Create a graph from a tetragon. 49 | let mut graph = MeshGraph::::from(Tetragon::from([ 50 | (1.0, 0.0, -1.0), 51 | (-1.0, 0.0, -1.0), 52 | (-1.0, 0.0, 1.0), 53 | (1.0, 0.0, 1.0), 54 | ])); 55 | // Get the face of the tetragon. 56 | let key = graph.faces().next().unwrap().key(); 57 | let mut face = graph.face_mut(key).unwrap(); 58 | 59 | // Subdivide and extrude the face repeatedly. 60 | for _ in 0..5 { 61 | face = face.ambo().extrude_with_offset(0.5).unwrap(); 62 | } 63 | 64 | // Convert the graph into a buffer. 65 | graph.triangulate(); 66 | graph 67 | .to_mesh_by_face_with(|face, vertex| Vertex { 68 | position: vertex.position().into_homogeneous().into(), 69 | normal: face.normal().unwrap().into_homogeneous().into(), 70 | color: [1.0, 0.6, 0.2, 1.0], 71 | }) 72 | .unwrap() 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /examples/teapot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-teapot" 3 | version = "0.0.0" 4 | edition = "2021" 5 | authors = ["Sean Olson "] 6 | license = "MIT" 7 | publish = false 8 | 9 | [dependencies] 10 | nalgebra = "^0.33.0" 11 | theon = "^0.1.0" 12 | 13 | [dependencies.pictor] 14 | path = "../../pictor" 15 | 16 | [dependencies.plexus] 17 | path = "../../plexus" 18 | default-features = false 19 | features = [ 20 | "encoding-ply", 21 | "geometry-nalgebra", 22 | ] 23 | -------------------------------------------------------------------------------- /examples/teapot/src/main.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Point3; 2 | use pictor::pipeline::{self, Vertex}; 3 | use plexus::encoding::ply::{FromPly, PositionEncoding}; 4 | use plexus::graph::MeshGraph; 5 | use theon::space::{EuclideanSpace, VectorSpace}; 6 | 7 | type E3 = Point3; 8 | 9 | fn main() { 10 | let from = Point3::new(0.0, -6.0, 4.0); 11 | let to = Point3::new(0.0, 0.0, 1.0); 12 | pipeline::render_mesh_buffer_with(from, to, move || { 13 | // Read PLY data into a graph. 14 | let ply: &[u8] = include_bytes!("../../../data/teapot.ply"); 15 | let encoding = PositionEncoding::::default(); 16 | let (graph, _) = MeshGraph::::from_ply(encoding, ply).expect("teapot"); 17 | 18 | // Convert the graph into a buffer. 19 | graph 20 | .to_mesh_by_vertex_with(|vertex| Vertex { 21 | position: vertex.position().into_homogeneous().into(), 22 | normal: vertex.normal().unwrap().into_homogeneous().into(), 23 | color: [1.0, 0.6, 0.2, 1.0], 24 | }) 25 | .expect("buffer") 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /pictor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pictor" 3 | version = "0.0.0" 4 | edition = "2021" 5 | rust-version = "1.81.0" 6 | authors = ["Sean Olson "] 7 | license = "MIT" 8 | description = "Renderer and support library for Plexus examples." 9 | publish = false 10 | 11 | [dependencies] 12 | bytemuck = "^1.13.0" 13 | decorum = "^0.4.0" 14 | naga = "^23.0.0" 15 | nalgebra = "^0.33.0" 16 | num = "^0.4.3" 17 | rand = "^0.8.5" 18 | theon = "^0.1.0" 19 | winit = "^0.30.8" 20 | 21 | [dependencies.futures] 22 | version = "^0.3.31" 23 | default-features = false 24 | features = [ 25 | "std", 26 | "executor", 27 | ] 28 | 29 | [dependencies.plexus] 30 | path = "../plexus" 31 | default-features = false 32 | features = ["geometry-nalgebra"] 33 | 34 | [dependencies.wgpu] 35 | version = "^23.0.1" 36 | features = ["spirv"] 37 | -------------------------------------------------------------------------------- /pictor/src/camera.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Isometry3, Matrix4, Orthographic3, Perspective3, Point3, Vector3}; 2 | use std::sync::LazyLock; 3 | use wgpu::SurfaceConfiguration; 4 | 5 | #[rustfmt::skip] 6 | static OPENGL_TO_WGPU_TRANSFORM: LazyLock> = LazyLock::new(|| { 7 | Matrix4::new( 8 | 1.0, 0.0, 0.0, 0.0, 9 | 0.0, 1.0, 0.0, 0.0, 10 | 0.0, 0.0, 0.5, 0.0, 11 | 0.0, 0.0, 0.5, 1.0, 12 | ) 13 | }); 14 | 15 | #[derive(Clone, Debug)] 16 | pub enum Projection { 17 | Perspective(Perspective3), 18 | Orthographic(Orthographic3), 19 | } 20 | 21 | impl Projection { 22 | pub fn perspective(aspect: f32, fov: f32, near: f32, far: f32) -> Self { 23 | Projection::Perspective(Perspective3::new(aspect, fov, near, far)) 24 | } 25 | 26 | pub fn orthographic(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Self { 27 | Projection::Orthographic(Orthographic3::new(left, right, bottom, top, near, far)) 28 | } 29 | } 30 | 31 | impl AsRef> for Projection { 32 | fn as_ref(&self) -> &Matrix4 { 33 | match *self { 34 | Projection::Perspective(ref perspective) => perspective.as_matrix(), 35 | Projection::Orthographic(ref orthographic) => orthographic.as_matrix(), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug)] 41 | pub struct Camera { 42 | pub projection: Projection, 43 | view: Isometry3, 44 | } 45 | 46 | impl Camera { 47 | pub fn look_at(&mut self, from: &Point3, to: &Point3) { 48 | self.view = Isometry3::look_at_rh(from, to, &Vector3::y()); 49 | } 50 | 51 | pub fn reproject(&mut self, surface: &SurfaceConfiguration) { 52 | match self.projection { 53 | Projection::Perspective(ref mut perspective) => { 54 | perspective.set_aspect(surface.width as f32 / surface.height as f32); 55 | } 56 | Projection::Orthographic(ref mut orthographic) => { 57 | let inverse = surface.height as f32 / surface.width as f32; 58 | let radius = (orthographic.right() - orthographic.left()) * inverse * 0.5; 59 | orthographic.set_bottom_and_top(-radius, radius); 60 | } 61 | } 62 | } 63 | 64 | pub fn transform(&self) -> Matrix4 { 65 | *OPENGL_TO_WGPU_TRANSFORM * self.projection.as_ref() * self.view.to_homogeneous() 66 | } 67 | } 68 | 69 | impl From for Camera { 70 | fn from(projection: Projection) -> Self { 71 | Camera { 72 | projection, 73 | view: Isometry3::look_at_rh( 74 | &Point3::new(0.0, 0.0, 1.0), 75 | &Point3::origin(), 76 | &Vector3::y(), 77 | ), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pictor/src/harness.rs: -------------------------------------------------------------------------------- 1 | use futures::executor::{self, LocalPool, LocalSpawner}; 2 | use futures::task::LocalSpawn; 3 | use std::cmp; 4 | use std::fmt::Debug; 5 | use std::sync::Arc; 6 | use winit::application::ApplicationHandler; 7 | use winit::dpi::PhysicalSize; 8 | use winit::event::WindowEvent; 9 | use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; 10 | use winit::window::{Window, WindowAttributes, WindowId}; 11 | 12 | use crate::renderer::Renderer; 13 | 14 | #[derive(Debug)] 15 | struct Executor { 16 | pool: LocalPool, 17 | spawner: LocalSpawner, 18 | } 19 | 20 | impl Executor { 21 | pub fn new() -> Self { 22 | let pool = LocalPool::new(); 23 | let spawner = pool.spawner(); 24 | Executor { pool, spawner } 25 | } 26 | 27 | pub fn flush(&mut self) { 28 | self.pool.run_until_stalled() 29 | } 30 | 31 | pub fn spawner(&self) -> &impl LocalSpawn { 32 | &self.spawner 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | struct Activity { 38 | window: Arc, 39 | renderer: Renderer<'static>, 40 | application: T, 41 | } 42 | 43 | impl Activity 44 | where 45 | T: Application, 46 | { 47 | pub fn bind_and_configure(window: Window, configuration: T::Configuration) -> Self { 48 | let window = Arc::new(window); 49 | let renderer = executor::block_on(Renderer::try_from_window(window.clone())).unwrap(); 50 | let application = T::configure(configuration, &renderer).unwrap(); 51 | Activity { 52 | window, 53 | renderer, 54 | application, 55 | } 56 | } 57 | } 58 | 59 | #[derive(Debug)] 60 | struct Harness { 61 | executor: Executor, 62 | f: F, 63 | activity: Option>, 64 | } 65 | 66 | impl Harness 67 | where 68 | T: Application, 69 | { 70 | fn redraw(&mut self) { 71 | if let Some(activity) = self.activity.as_mut() { 72 | let frame = activity.renderer.surface.get_current_texture().unwrap(); 73 | let view = frame 74 | .texture 75 | .create_view(&wgpu::TextureViewDescriptor::default()); 76 | activity 77 | .application 78 | .render(&activity.renderer, &view, self.executor.spawner()); 79 | frame.present(); 80 | } 81 | } 82 | 83 | fn resize(&mut self, dimensions: PhysicalSize) { 84 | if let Some(activity) = self.activity.as_mut() { 85 | activity.renderer.surface_configuration.width = cmp::max(1, dimensions.width); 86 | activity.renderer.surface_configuration.height = cmp::max(1, dimensions.height); 87 | activity.application.resize(&activity.renderer); 88 | activity.renderer.surface.configure( 89 | &activity.renderer.device, 90 | &activity.renderer.surface_configuration, 91 | ); 92 | activity.window.request_redraw(); 93 | } 94 | } 95 | } 96 | 97 | impl ApplicationHandler<()> for Harness 98 | where 99 | T: Application, 100 | F: FnMut() -> (WindowAttributes, T::Configuration), 101 | { 102 | fn resumed(&mut self, reactor: &ActiveEventLoop) { 103 | let (window, configuration) = (self.f)(); 104 | self.activity.replace(Activity::bind_and_configure( 105 | reactor.create_window(window).unwrap(), 106 | configuration, 107 | )); 108 | } 109 | 110 | fn suspended(&mut self, _reactor: &ActiveEventLoop) { 111 | self.activity.take(); 112 | } 113 | 114 | fn window_event(&mut self, reactor: &ActiveEventLoop, _: WindowId, event: WindowEvent) { 115 | match event { 116 | WindowEvent::RedrawRequested => { 117 | self.redraw(); 118 | } 119 | WindowEvent::CloseRequested => { 120 | reactor.exit(); 121 | } 122 | WindowEvent::Resized(dimensions) => { 123 | self.resize(dimensions); 124 | } 125 | _ => { 126 | if let Some(activity) = self.activity.as_mut() { 127 | if let Reaction::Abort = activity.application.react(event) { 128 | reactor.exit(); 129 | } 130 | } 131 | } 132 | } 133 | self.executor.flush(); 134 | } 135 | } 136 | 137 | #[derive(Clone, Copy, Debug, Default)] 138 | pub enum Reaction { 139 | #[default] 140 | Continue, 141 | Abort, 142 | } 143 | 144 | pub trait ConfigureStage { 145 | fn device(&self) -> &wgpu::Device; 146 | 147 | fn queue(&self) -> &wgpu::Queue; 148 | 149 | fn surface_configuration(&self) -> &wgpu::SurfaceConfiguration; 150 | } 151 | 152 | pub trait RenderStage { 153 | fn device(&self) -> &wgpu::Device; 154 | 155 | fn queue(&self) -> &wgpu::Queue; 156 | } 157 | 158 | pub trait Application: 'static + Sized { 159 | type Configuration: Sized; 160 | type Error: Debug; 161 | 162 | fn configure( 163 | configuration: Self::Configuration, 164 | stage: &impl ConfigureStage, 165 | ) -> Result; 166 | 167 | fn react(&mut self, event: WindowEvent) -> Reaction { 168 | let _ = event; 169 | Reaction::Continue 170 | } 171 | 172 | fn resize(&mut self, stage: &impl ConfigureStage); 173 | 174 | fn render( 175 | &mut self, 176 | stage: &impl RenderStage, 177 | view: &wgpu::TextureView, 178 | spawn: &impl LocalSpawn, 179 | ); 180 | } 181 | 182 | pub fn run(f: F) 183 | where 184 | T: Application, 185 | F: FnMut() -> (WindowAttributes, T::Configuration), 186 | { 187 | let executor = Executor::new(); 188 | let reactor = EventLoop::new().unwrap(); 189 | reactor.set_control_flow(ControlFlow::Poll); 190 | reactor 191 | .run_app(&mut Harness:: { 192 | executor, 193 | f, 194 | activity: None, 195 | }) 196 | .unwrap(); 197 | } 198 | -------------------------------------------------------------------------------- /pictor/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod camera; 2 | mod harness; 3 | pub mod pipeline; 4 | mod renderer; 5 | 6 | pub use crate::camera::*; 7 | pub use crate::harness::*; 8 | 9 | // TODO: Compile shaders from source code rather than statically loading binary SpirV. 10 | -------------------------------------------------------------------------------- /pictor/src/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use winit::window::Window; 3 | 4 | use crate::harness::{ConfigureStage, RenderStage}; 5 | 6 | #[derive(Debug)] 7 | pub struct Renderer<'window> { 8 | _instance: wgpu::Instance, 9 | _adapter: wgpu::Adapter, 10 | pub surface: wgpu::Surface<'window>, 11 | pub surface_configuration: wgpu::SurfaceConfiguration, 12 | pub device: wgpu::Device, 13 | queue: wgpu::Queue, 14 | } 15 | 16 | impl<'window> Renderer<'window> { 17 | pub async fn try_from_window(window: T) -> Result 18 | where 19 | T: Borrow + Into>, 20 | { 21 | let dimensions = window.borrow().inner_size(); 22 | let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); 23 | let surface = instance.create_surface(window).unwrap(); 24 | let adapter = instance 25 | .request_adapter(&wgpu::RequestAdapterOptions { 26 | compatible_surface: Some(&surface), 27 | ..Default::default() 28 | }) 29 | .await 30 | .ok_or(())?; 31 | let (device, queue) = adapter 32 | .request_device( 33 | &wgpu::DeviceDescriptor { 34 | required_features: wgpu::Features::SPIRV_SHADER_PASSTHROUGH, 35 | required_limits: wgpu::Limits::downlevel_webgl2_defaults() 36 | .using_resolution(adapter.limits()), 37 | memory_hints: wgpu::MemoryHints::MemoryUsage, 38 | ..Default::default() 39 | }, 40 | None, 41 | ) 42 | .await 43 | .map_err(|_| ())?; 44 | let surface_configuration = surface 45 | .get_default_config(&adapter, dimensions.width, dimensions.height) 46 | .unwrap(); 47 | Ok(Renderer { 48 | _instance: instance, 49 | _adapter: adapter, 50 | surface, 51 | surface_configuration, 52 | device, 53 | queue, 54 | }) 55 | } 56 | } 57 | 58 | impl ConfigureStage for Renderer<'_> { 59 | fn device(&self) -> &wgpu::Device { 60 | &self.device 61 | } 62 | 63 | fn queue(&self) -> &wgpu::Queue { 64 | &self.queue 65 | } 66 | 67 | fn surface_configuration(&self) -> &wgpu::SurfaceConfiguration { 68 | &self.surface_configuration 69 | } 70 | } 71 | 72 | impl RenderStage for Renderer<'_> { 73 | fn device(&self) -> &wgpu::Device { 74 | &self.device 75 | } 76 | 77 | fn queue(&self) -> &wgpu::Queue { 78 | &self.queue 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pictor/src/shader.glsl.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec4 v_color; 4 | 5 | layout(location = 0) out vec4 f_target0; 6 | 7 | void main() { 8 | f_target0 = v_color; 9 | } 10 | -------------------------------------------------------------------------------- /pictor/src/shader.glsl.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec4 a_position; 4 | layout(location = 1) in vec4 a_normal; 5 | layout(location = 2) in vec4 a_color; 6 | 7 | layout(set = 0, binding = 0) uniform transform { 8 | mat4 u_transform; 9 | }; 10 | layout(set = 0, binding = 1) uniform viewpoint { 11 | vec3 u_viewpoint; 12 | }; 13 | 14 | layout(location = 0) out vec4 v_color; 15 | 16 | void main() { 17 | v_color = a_color * vec4(vec3(max(0.0, dot(vec3(a_normal), normalize(u_viewpoint - vec3(a_position))))), 1.0); 18 | gl_Position = u_transform * a_position; 19 | } 20 | -------------------------------------------------------------------------------- /pictor/src/shader.spv.frag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olson-sean-k/plexus/b4e4a4b0fb0948101f2b15ca164027053123198b/pictor/src/shader.spv.frag -------------------------------------------------------------------------------- /pictor/src/shader.spv.vert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olson-sean-k/plexus/b4e4a4b0fb0948101f2b15ca164027053123198b/pictor/src/shader.spv.vert -------------------------------------------------------------------------------- /plexus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plexus" 3 | version = "0.0.11" 4 | edition = "2021" 5 | rust-version = "1.81.0" 6 | authors = ["Sean Olson "] 7 | license = "MIT" 8 | readme = "../README.md" 9 | homepage = "https://plexus.rs" 10 | repository = "https://github.com/olson-sean-k/plexus" 11 | description = "Polygonal mesh processing." 12 | keywords = [ 13 | "polygon", 14 | "mesh", 15 | "topology", 16 | "geometry", 17 | "half-edge", 18 | ] 19 | categories = [ 20 | "algorithms", 21 | "data-structures", 22 | "graphics", 23 | "rendering::data-formats", 24 | ] 25 | 26 | [package.metadata.docs.rs] 27 | # Enable all features for API documentation. 28 | all-features = true 29 | # Enable KaTeX support by injecting a header into the documentation. 30 | rustdoc-args = [ 31 | "--html-in-header", 32 | "../doc/katex-header.html", 33 | ] 34 | 35 | [[bench]] 36 | name = "subdivide" 37 | harness = false 38 | path = "../benches/subdivide.rs" 39 | 40 | [features] 41 | default = [] 42 | encoding-ply = ["dep:ply-rs"] 43 | geometry-cgmath = [ 44 | "dep:cgmath", 45 | "theon/cgmath", 46 | ] 47 | geometry-glam = [ 48 | "dep:glam", 49 | "theon/glam", 50 | ] 51 | geometry-mint = [ 52 | "dep:mint", 53 | "theon/mint", 54 | ] 55 | geometry-nalgebra = [ 56 | "dep:nalgebra", 57 | "theon/nalgebra", 58 | ] 59 | geometry-ultraviolet = [ 60 | "dep:ultraviolet", 61 | "theon/ultraviolet", 62 | ] 63 | 64 | [dependencies] 65 | approx = "^0.5.0" 66 | ahash = "^0.8.11" 67 | arrayvec = "^0.7.6" 68 | derivative = "^2.1.1" 69 | itertools = "^0.14.0" 70 | num = "^0.4.3" 71 | smallvec = "^1.0.0" 72 | thiserror = "^2.0.10" 73 | typenum = "^1.17.0" 74 | 75 | [dependencies.cgmath] 76 | version = "^0.18.0" 77 | optional = true 78 | 79 | [dependencies.decorum] 80 | version = "^0.4.0" 81 | default-features = false 82 | features = [ 83 | "approx", 84 | "serde", 85 | "std", 86 | ] 87 | 88 | [dependencies.glam] 89 | version = "^0.29.0" 90 | optional = true 91 | 92 | [dependencies.mint] 93 | version = "^0.5.0" 94 | optional = true 95 | 96 | [dependencies.nalgebra] 97 | version = "^0.33.0" 98 | optional = true 99 | 100 | [dependencies.ply-rs] 101 | version = "^0.1.2" 102 | optional = true 103 | 104 | [dependencies.theon] 105 | version = "^0.1.0" 106 | default-features = false 107 | features = ["lapack"] 108 | 109 | [dependencies.ultraviolet] 110 | version = "^0.9.0" 111 | optional = true 112 | 113 | [build-dependencies] 114 | rustversion = "^1.0.3" 115 | 116 | [dev-dependencies] 117 | criterion = "^0.5.1" 118 | # For brevity and simplicity, tests and API documentation use a direct 119 | # dependency on `nalgebra`. This approach requires that the version 120 | # specification is compatible with `theon`. 121 | nalgebra = "^0.33.0" 122 | 123 | [dev-dependencies.theon] 124 | version = "^0.1.0" 125 | default-features = false 126 | features = [ 127 | "lapack", 128 | "nalgebra", 129 | ] 130 | -------------------------------------------------------------------------------- /plexus/src/buffer/builder.rs: -------------------------------------------------------------------------------- 1 | use num::{Integer, NumCast, Unsigned}; 2 | use std::hash::Hash; 3 | use typenum::NonZero; 4 | 5 | use crate::buffer::{BufferError, MeshBuffer}; 6 | use crate::builder::{FacetBuilder, MeshBuilder, SurfaceBuilder}; 7 | use crate::constant::{Constant, ToType, TypeOf}; 8 | use crate::geometry::{FromGeometry, IntoGeometry}; 9 | use crate::index::{Flat, Grouping, IndexBuffer}; 10 | use crate::primitive::Topological; 11 | use crate::transact::{ClosedInput, Transact}; 12 | use crate::Arity; 13 | 14 | // TODO: It should not be possible to manufacture keys without placing 15 | // additional constraints on the type bounds of `FacetBuilder` (for 16 | // example, `FacetBuilder`). Is it important to check for 17 | // out-of-bounds indices in `insert_facet`? 18 | 19 | pub type VertexKey = ::Group> as IndexBuffer>::Index; 20 | 21 | pub struct BufferBuilder 22 | where 23 | R: Grouping, 24 | { 25 | indices: Vec, 26 | vertices: Vec, 27 | } 28 | 29 | impl Default for BufferBuilder 30 | where 31 | R: Grouping, 32 | Vec: IndexBuffer, 33 | { 34 | fn default() -> Self { 35 | BufferBuilder { 36 | indices: Default::default(), 37 | vertices: Default::default(), 38 | } 39 | } 40 | } 41 | 42 | impl ClosedInput for BufferBuilder 43 | where 44 | R: Grouping, 45 | Vec: IndexBuffer, 46 | { 47 | type Input = (); 48 | } 49 | 50 | impl FacetBuilder for BufferBuilder, G> 51 | where 52 | Constant: ToType, 53 | TypeOf: NonZero, 54 | K: Copy + Hash + Integer + Unsigned, 55 | Vec: IndexBuffer>, 56 | { 57 | type Facet = (); 58 | type Key = (); 59 | 60 | fn insert_facet(&mut self, keys: T, _: U) -> Result 61 | where 62 | Self::Facet: FromGeometry, 63 | T: AsRef<[K]>, 64 | { 65 | let keys = keys.as_ref(); 66 | if keys.len() == N { 67 | self.indices.extend(keys.iter()); 68 | Ok(()) 69 | } 70 | else { 71 | // TODO: These numbers do not necessarily represent arity (i.e., the 72 | // number of edges of each topological structure). Use a 73 | // different error variant to express this. 74 | Err(BufferError::ArityConflict { 75 | expected: N, 76 | actual: keys.len(), 77 | }) 78 | } 79 | } 80 | } 81 | 82 | impl FacetBuilder for BufferBuilder 83 | where 84 | P: Grouping + Topological, 85 | P::Vertex: Copy + Hash + Integer + Unsigned, 86 | Vec

: IndexBuffer

, 87 | { 88 | type Facet = (); 89 | type Key = (); 90 | 91 | fn insert_facet(&mut self, keys: T, _: U) -> Result 92 | where 93 | Self::Facet: FromGeometry, 94 | T: AsRef<[P::Vertex]>, 95 | { 96 | let arity = keys.as_ref().len(); 97 | P::try_from_slice(keys) 98 | .ok_or(BufferError::ArityConflict { 99 | expected: P::ARITY.into_interval().0, 100 | actual: arity, 101 | }) 102 | .map(|polygon| self.indices.push(polygon)) 103 | } 104 | } 105 | 106 | impl MeshBuilder for BufferBuilder 107 | where 108 | Self: SurfaceBuilder, 109 | R: Grouping, 110 | VertexKey: Hash, 111 | Vec: IndexBuffer, 112 | { 113 | type Builder = Self; 114 | 115 | type Vertex = G; 116 | type Facet = (); 117 | 118 | fn surface_with(&mut self, f: F) -> Result 119 | where 120 | Self::Error: From, 121 | F: FnOnce(&mut Self::Builder) -> Result, 122 | { 123 | f(self).map_err(|error| error.into()) 124 | } 125 | } 126 | 127 | impl SurfaceBuilder for BufferBuilder 128 | where 129 | Self: FacetBuilder, Facet = ()>, 130 | Self::Error: From, // TODO: Why is this necessary? 131 | R: Grouping, 132 | VertexKey: Hash + NumCast, 133 | Vec: IndexBuffer, 134 | { 135 | type Builder = Self; 136 | type Key = VertexKey; 137 | 138 | type Vertex = G; 139 | type Facet = (); 140 | 141 | fn facets_with(&mut self, f: F) -> Result 142 | where 143 | Self::Error: From, 144 | F: FnOnce(&mut Self::Builder) -> Result, 145 | { 146 | f(self).map_err(|error| error.into()) 147 | } 148 | 149 | fn insert_vertex(&mut self, data: T) -> Result 150 | where 151 | Self::Vertex: FromGeometry, 152 | { 153 | let key = as NumCast>::from(self.vertices.len()) 154 | .ok_or(BufferError::IndexOverflow)?; 155 | self.vertices.push(data.into_geometry()); 156 | Ok(key) 157 | } 158 | } 159 | 160 | impl Transact<::Input> for BufferBuilder 161 | where 162 | R: Grouping, 163 | Vec: IndexBuffer, 164 | { 165 | type Commit = MeshBuffer; 166 | type Abort = (); 167 | type Error = BufferError; 168 | 169 | fn commit(self) -> Result { 170 | let BufferBuilder { indices, vertices } = self; 171 | Ok(MeshBuffer::from_raw_buffers_unchecked(indices, vertices)) 172 | } 173 | 174 | fn abort(self) -> Self::Abort {} 175 | } 176 | -------------------------------------------------------------------------------- /plexus/src/builder.rs: -------------------------------------------------------------------------------- 1 | //! Incremental polygonal mesh construction. 2 | //! 3 | //! This module provides traits for incrementally constructing mesh data 4 | //! structures. This API allows for meshes to be constructed in a way that is 5 | //! agnostic to the specific data structure used to represent the mesh. 6 | //! 7 | //! [`Buildable`] is the primary trait of this API. It is implemented by mesh 8 | //! data structures and exposes various associated types for their associated 9 | //! data. [`Buildable`] exposes a builder type via its 10 | //! [`builder`][`Buildable::builder`] function. This builder type in turn 11 | //! provides additional builders that can be used to construct a mesh from 12 | //! _surfaces_ and _facets_. 13 | //! 14 | //! # Examples 15 | //! 16 | //! A function that generates a triangle from point geometry using builders: 17 | //! 18 | //! ```rust 19 | //! # extern crate nalgebra; 20 | //! # extern crate plexus; 21 | //! # 22 | //! use nalgebra::Point2; 23 | //! use plexus::buffer::MeshBuffer3; 24 | //! use plexus::builder::Buildable; 25 | //! use plexus::geometry::FromGeometry; 26 | //! use plexus::graph::MeshGraph; 27 | //! use plexus::prelude::*; 28 | //! 29 | //! type E2 = Point2; 30 | //! 31 | //! fn trigon(points: [T; 3]) -> Result 32 | //! where 33 | //! B: Buildable, 34 | //! B::Vertex: FromGeometry, 35 | //! { 36 | //! let mut builder = B::builder(); 37 | //! builder.surface_with(|builder| { 38 | //! let [a, b, c] = points; 39 | //! let a = builder.insert_vertex(a)?; 40 | //! let b = builder.insert_vertex(b)?; 41 | //! let c = builder.insert_vertex(c)?; 42 | //! builder.facets_with(|builder| builder.insert_facet(&[a, b, c], B::Facet::default())) 43 | //! })?; 44 | //! builder.build() 45 | //! } 46 | //! 47 | //! // `MeshBuffer` and `MeshGraph` implement the `Buildable` trait. 48 | //! let graph: MeshGraph = trigon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]).unwrap(); 49 | //! let buffer: MeshBuffer3 = trigon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]).unwrap(); 50 | //! ``` 51 | //! 52 | //! [`Buildable::builder`]: crate::builder::Buildable::builder 53 | //! [`Buildable`]: crate::builder::Buildable 54 | 55 | // TODO: Is it useful to use a separate `FacetBuilder` type? 56 | // TODO: Keys are not opaque. Especially for `MeshBuffer`, it may be possible to 57 | // "forge" keys. This could be prevented by using a wrapper type that is 58 | // not exported, but would introduce a performance cost to map and collect 59 | // slices of keys. 60 | 61 | use std::fmt::Debug; 62 | use std::hash::Hash; 63 | 64 | use crate::geometry::FromGeometry; 65 | use crate::transact::ClosedInput; 66 | 67 | /// Polygonal mesh data structure that can be built incrementally. 68 | /// 69 | /// This trait is the primary entrypoint into the builder API. Types that 70 | /// implement this trait expose a [`MeshBuilder`] that can be used to construct 71 | /// an instance of the type from surfaces and facets. 72 | /// 73 | /// [`MeshBuilder`]: crate::builder::MeshBuilder 74 | pub trait Buildable: Sized { 75 | type Builder: MeshBuilder< 76 | Commit = Self, 77 | Error = Self::Error, 78 | Vertex = Self::Vertex, 79 | Facet = Self::Facet, 80 | >; 81 | type Error: Debug; 82 | 83 | /// Vertex data. 84 | /// 85 | /// This type represents the data associated with vertices in the mesh. 86 | /// This typically includes positional data, but no data is required and 87 | /// this type may be the unit type `()`. 88 | /// 89 | /// Each builder trait also exposes such an associated type which is 90 | /// constrained by the `Builder` type. 91 | type Vertex; 92 | /// Facet data. 93 | /// 94 | /// This type represents the data associated with facets in the mesh. No 95 | /// data is required and this type may be the unit type `()`. 96 | /// 97 | /// Each builder trait also exposes such an associated type which is 98 | /// constrained by the `Builder` type. 99 | type Facet: Default; 100 | 101 | fn builder() -> Self::Builder; 102 | } 103 | 104 | /// Incremental polygonal mesh builder. 105 | /// 106 | /// This trait exposes types that allow for mesh data structures to be 107 | /// constructed incrementally from _surfaces_ and _facets_. A _surface_ is a 108 | /// collection of vertices and facets connecting those vertices and typically 109 | /// describes a _manifold_. A _facet_ is the connectivity between vertices in a 110 | /// surface. Facets may also include associated data. 111 | /// 112 | /// Construction is hierarchical, beginning with a surface and its vertices and 113 | /// then facets. The association between a surface, its vertices, and facets is 114 | /// enforced by the API, which accepts functions that operate on increasingly 115 | /// specific builder types. The [`build`][`MeshBuilder::build`] function is used 116 | /// to complete the construction of a mesh. 117 | /// 118 | /// Builders may emit errors at any stage and errors depend on the 119 | /// implementation of the builder types (and by extension the details of the 120 | /// underlying data structure). 121 | /// 122 | /// [`MeshBuilder::build`]: crate::builder::MeshBuilder::build 123 | pub trait MeshBuilder: ClosedInput { 124 | type Builder: SurfaceBuilder; 125 | 126 | type Vertex; 127 | type Facet: Default; 128 | 129 | /// Constructs a surface. 130 | /// 131 | /// The given function is invoked with a [`SurfaceBuilder`], which can be 132 | /// used to insert vertices and construct facets. 133 | /// 134 | /// [`SurfaceBuilder`]: crate::builder::SurfaceBuilder 135 | fn surface_with(&mut self, f: F) -> Result 136 | where 137 | Self::Error: From, 138 | F: FnOnce(&mut Self::Builder) -> Result; 139 | 140 | /// Builds the mesh. 141 | /// 142 | /// The builder is consumed and a mesh with the constructed surfaces and 143 | /// facets is produced. 144 | /// 145 | /// # Errors 146 | /// 147 | /// Returns a latent error if the constructed surfaces and facets are 148 | /// incompatible with the underlying data structure. May return other 149 | /// errors depending on the details of the implementation. 150 | fn build(self) -> Result { 151 | self.commit().map_err(|(_, error)| error) 152 | } 153 | } 154 | 155 | pub trait SurfaceBuilder: ClosedInput { 156 | type Builder: FacetBuilder; 157 | /// Vertex key. 158 | /// 159 | /// Each vertex is associated with a key of this type. This key is used to 160 | /// reference a given vertex and is required to insert faces with a 161 | /// [`FacetBuilder`]. 162 | /// 163 | /// [`FacetBuilder`]: crate::builder::FacetBuilder 164 | type Key: Copy + Eq + Hash; 165 | 166 | type Vertex; 167 | type Facet: Default; 168 | 169 | /// Constructs facets in the surface. 170 | /// 171 | /// The given function is invoked with a [`FacetBuilder`], which can be used 172 | /// to insert facets. 173 | /// 174 | /// [`FacetBuilder`]: crate::builder::FacetBuilder 175 | fn facets_with(&mut self, f: F) -> Result 176 | where 177 | Self::Error: From, 178 | F: FnOnce(&mut Self::Builder) -> Result; 179 | 180 | /// Inserts a vertex into the surface. 181 | /// 182 | /// Returns a key that refers to the inserted vertex. This key can be used 183 | /// to insert facets with a [`FacetBuilder`]. 184 | /// 185 | /// [`FacetBuilder`]: crate::builder::FacetBuilder 186 | fn insert_vertex(&mut self, data: T) -> Result 187 | where 188 | Self::Vertex: FromGeometry; 189 | } 190 | 191 | pub trait FacetBuilder: ClosedInput 192 | where 193 | K: Eq + Hash, 194 | { 195 | /// Facet key. 196 | /// 197 | /// Each facet is associated with a key of this type. 198 | type Key: Copy + Eq + Hash; 199 | 200 | type Facet: Default; 201 | 202 | /// Inserts a facet into the associated surface. 203 | /// 204 | /// A facet is formed from connectivity between vertices represented by an 205 | /// ordered slice of vertex keys from the associated [`SurfaceBuilder`]. 206 | /// 207 | /// Returns a key that refers to the inserted facet. 208 | /// 209 | /// [`SurfaceBuilder`]: crate::builder::SurfaceBuilder 210 | fn insert_facet(&mut self, keys: T, data: U) -> Result 211 | where 212 | Self::Facet: FromGeometry, 213 | T: AsRef<[K]>; 214 | } 215 | -------------------------------------------------------------------------------- /plexus/src/constant.rs: -------------------------------------------------------------------------------- 1 | //! Morphisms between constant generics and numeric types. 2 | //! 3 | //! This module provides conversions between `typenum`'s unsigned integer types 4 | //! and `usize` constant generics. These conversions are necessary to perform 5 | //! static computations and comparisons, which cannot yet be done using constant 6 | //! generics alone at the time of writing (e.g., `{N >= 3}`). 7 | //! 8 | //! See discussion on the [Rust internals 9 | //! forum](https://internals.rust-lang.org/t/const-generics-where-restrictions/12742/7). 10 | 11 | // TODO: Move this into the `theon` crate as part of its public API. 12 | 13 | pub type ConstantOf = ::Output; 14 | pub type TypeOf = as ToType>::Output; 15 | 16 | pub struct Constant; 17 | 18 | pub trait ToConstant { 19 | type Output; 20 | } 21 | 22 | pub trait ToType { 23 | type Output; 24 | } 25 | 26 | macro_rules! impl_morphisms { 27 | (types => $($n:ident),*$(,)?) => ( 28 | use typenum::Unsigned; 29 | 30 | $( 31 | impl ToConstant for typenum::$n { 32 | type Output = Constant<{ typenum::$n::USIZE }>; 33 | } 34 | 35 | impl ToType for Constant<{ typenum::$n::USIZE }> { 36 | type Output = typenum::$n; 37 | } 38 | )* 39 | ); 40 | } 41 | impl_morphisms!(types => 42 | U0, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18, U19, U21, 43 | U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32, 44 | ); 45 | -------------------------------------------------------------------------------- /plexus/src/encoding/mod.rs: -------------------------------------------------------------------------------- 1 | //! Serialization and encodings. 2 | //! 3 | //! This module provides encoding support enabled via Cargo features. Each 4 | //! enabled encoding has a corresponding sub-module. For example, when [PLY] 5 | //! support is enabled, the `ply` module is exposed. The following table 6 | //! summarizes the encodings supported by Plexus: 7 | //! 8 | //! | Feature | Default | Encoding | Read | Write | 9 | //! |----------------|---------|----------|------|-------| 10 | //! | `encoding-ply` | No | [PLY] | Yes | No | 11 | //! 12 | //! This module provides traits used by all encodings. These traits describe the 13 | //! outputs and inputs of decoders and encoders, respectively. Generally, these 14 | //! traits should **not** be used directly. Instead, prefer the conversion 15 | //! traits exposed for specific encodings, such as `FromPly` when using [PLY]. 16 | //! 17 | //! [PLY]: https://en.wikipedia.org/wiki/ply_(file_format) 18 | 19 | pub mod ply; 20 | 21 | use std::fmt::Debug; 22 | 23 | pub trait VertexDecoder { 24 | type Output: IntoIterator; 25 | type Vertex; 26 | } 27 | 28 | pub trait FaceDecoder { 29 | type Output: IntoIterator; 30 | type Index: IntoIterator; 31 | type Face; 32 | } 33 | 34 | // TODO: This trait is a bit limiting. Consider implementing more specific 35 | // traits like `FromPly` directly. This could allow more specific 36 | // features to be supported, such as edge geometry for `MeshGraph`s. 37 | pub trait FromEncoding: Sized 38 | where 39 | E: FaceDecoder + VertexDecoder, 40 | { 41 | type Error: Debug; 42 | 43 | fn from_encoding( 44 | vertices: ::Output, 45 | faces: ::Output, 46 | ) -> Result; 47 | } 48 | -------------------------------------------------------------------------------- /plexus/src/entity/borrow.rs: -------------------------------------------------------------------------------- 1 | pub trait Reborrow { 2 | type Target; 3 | 4 | fn reborrow(&self) -> &Self::Target; 5 | } 6 | 7 | pub trait ReborrowMut: Reborrow { 8 | fn reborrow_mut(&mut self) -> &mut Self::Target; 9 | } 10 | 11 | pub trait ReborrowInto<'a>: Reborrow { 12 | fn reborrow_into(self) -> &'a Self::Target; 13 | } 14 | 15 | impl Reborrow for &'_ T { 16 | type Target = T; 17 | 18 | fn reborrow(&self) -> &Self::Target { 19 | self 20 | } 21 | } 22 | 23 | impl Reborrow for &'_ mut T { 24 | type Target = T; 25 | 26 | fn reborrow(&self) -> &Self::Target { 27 | self 28 | } 29 | } 30 | 31 | impl ReborrowMut for &'_ mut T { 32 | fn reborrow_mut(&mut self) -> &mut Self::Target { 33 | self 34 | } 35 | } 36 | 37 | impl<'a, T> ReborrowInto<'a> for &'a T { 38 | fn reborrow_into(self) -> &'a Self::Target { 39 | self 40 | } 41 | } 42 | 43 | impl<'a, T> ReborrowInto<'a> for &'a mut T { 44 | fn reborrow_into(self) -> &'a Self::Target { 45 | &*self 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /plexus/src/entity/dijkstra.rs: -------------------------------------------------------------------------------- 1 | use derivative::Derivative; 2 | use std::cmp::Reverse; 3 | use std::collections::hash_map::Entry; 4 | use std::collections::{BinaryHeap, HashMap, HashSet}; 5 | 6 | use crate::entity::storage::{AsStorage, Enumerate, Get}; 7 | use crate::entity::traverse::Adjacency; 8 | use crate::entity::view::{Bind, Unbind}; 9 | use crate::entity::EntityError; 10 | use crate::geometry::Metric; 11 | 12 | pub type MetricTree = HashMap, Q)>; 13 | 14 | #[derive(Derivative)] 15 | #[derivative(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] 16 | struct KeyedMetric( 17 | #[derivative(Ord = "ignore", PartialEq = "ignore", PartialOrd = "ignore")] K, 18 | Q, 19 | ) 20 | where 21 | Q: Eq + Ord; 22 | 23 | pub fn metrics_with<'a, M, T, Q, F>( 24 | from: T, 25 | to: Option, 26 | f: F, 27 | ) -> Result, EntityError> 28 | where 29 | M: 'a + AsStorage, 30 | T: Adjacency + Bind<&'a M> + Copy + Unbind<&'a M>, 31 | Q: Copy + Metric, 32 | F: Fn(T, T) -> Q, 33 | { 34 | let (storage, from) = from.unbind(); 35 | let capacity = if let Some(key) = to { 36 | if !storage.as_storage().contains_key(&key) { 37 | return Err(EntityError::EntityNotFound); 38 | } 39 | 0 40 | } 41 | else { 42 | storage.as_storage().len() 43 | }; 44 | let mut buffer = BinaryHeap::new(); 45 | let mut breadcrumbs = HashSet::with_capacity(capacity); 46 | let mut metrics = HashMap::with_capacity(capacity); 47 | 48 | metrics.insert(from, (None, Q::zero())); 49 | buffer.push(KeyedMetric(from, Reverse(Q::zero()))); 50 | while let Some(KeyedMetric(key, Reverse(metric))) = buffer.pop() { 51 | if Some(key) == to { 52 | break; 53 | } 54 | let entity = T::bind(storage, key).ok_or(EntityError::EntityNotFound)?; 55 | if breadcrumbs.insert(entity.key()) { 56 | for adjacent in entity 57 | .adjacency() 58 | .into_iter() 59 | .map(|key| T::bind(storage, key)) 60 | { 61 | let adjacent = adjacent.ok_or(EntityError::EntityNotFound)?; 62 | let summand = f(entity, adjacent); 63 | if summand < Q::zero() { 64 | return Err(EntityError::Data); 65 | } 66 | let metric = metric + summand; 67 | match metrics.entry(adjacent.key()) { 68 | Entry::Occupied(entry) => { 69 | if metric < entry.get().1 { 70 | entry.into_mut().1 = metric; 71 | } 72 | } 73 | Entry::Vacant(entry) => { 74 | entry.insert((Some(entity.key()), metric)); 75 | } 76 | } 77 | buffer.push(KeyedMetric(adjacent.key(), Reverse(metric))); 78 | } 79 | } 80 | } 81 | Ok(metrics) 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use decorum::R64; 87 | use nalgebra::Point2; 88 | use theon::space::InnerSpace; 89 | 90 | use crate::entity::dijkstra::{self, MetricTree}; 91 | use crate::entity::EntityError; 92 | use crate::graph::MeshGraph; 93 | use crate::prelude::*; 94 | use crate::primitive::{Tetragon, Trigon}; 95 | 96 | #[test] 97 | fn decreasing_summand() { 98 | let graph = MeshGraph::>::from_raw_buffers( 99 | vec![Tetragon::new(0usize, 1, 2, 3)], 100 | vec![(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)], 101 | ) 102 | .unwrap(); 103 | let vertex = graph.vertices().next().unwrap(); 104 | assert_eq!( 105 | Err(EntityError::Data), 106 | dijkstra::metrics_with(vertex, None, |_, _| -1isize) 107 | ) 108 | } 109 | 110 | #[test] 111 | fn logical_metrics() { 112 | let graph = MeshGraph::<()>::from_raw_buffers(vec![Trigon::new(0usize, 1, 2)], vec![(); 3]) 113 | .unwrap(); 114 | let vertex = graph.vertices().next().unwrap(); 115 | let metrics = dijkstra::metrics_with(vertex, None, |_, _| 1usize).unwrap(); 116 | let a = vertex.key(); 117 | let b = vertex.outgoing_arc().destination_vertex().key(); 118 | let c = vertex.outgoing_arc().next_arc().destination_vertex().key(); 119 | let aq = *metrics.get(&a).unwrap(); 120 | let bq = *metrics.get(&b).unwrap(); 121 | let cq = *metrics.get(&c).unwrap(); 122 | 123 | assert_eq!(aq, (None, 0)); 124 | assert_eq!(bq, (Some(a), 1)); 125 | assert_eq!(cq, (Some(a), 1)); 126 | } 127 | 128 | // TODO: Use approximated comparisons in assertions. 129 | #[test] 130 | fn euclidean_distance_metrics() { 131 | let graph = MeshGraph::>::from_raw_buffers( 132 | vec![Tetragon::new(0usize, 1, 2, 3)], 133 | vec![(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)], 134 | ) 135 | .unwrap(); 136 | let vertex = graph.vertices().next().unwrap(); 137 | let metrics: MetricTree<_, R64> = dijkstra::metrics_with(vertex, None, |from, to| { 138 | R64::assert((to.position() - from.position()).magnitude()) 139 | }) 140 | .unwrap(); 141 | let a = vertex.key(); 142 | let b = vertex.outgoing_arc().destination_vertex().key(); 143 | let c = vertex.outgoing_arc().next_arc().destination_vertex().key(); 144 | let d = vertex 145 | .outgoing_arc() 146 | .next_arc() 147 | .next_arc() 148 | .destination_vertex() 149 | .key(); 150 | let aq = *metrics.get(&a).unwrap(); 151 | let bq = *metrics.get(&b).unwrap(); 152 | let cq = *metrics.get(&c).unwrap(); 153 | let dq = *metrics.get(&d).unwrap(); 154 | 155 | assert_eq!(aq, (None, R64::assert(0.0))); 156 | assert_eq!(bq, (Some(a), R64::assert(2.0))); 157 | assert_eq!(cq, (Some(b), R64::assert(4.0))); 158 | assert_eq!(dq, (Some(a), R64::assert(2.0))); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /plexus/src/entity/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod borrow; 2 | pub mod dijkstra; 3 | pub mod storage; 4 | pub mod traverse; 5 | pub mod view; 6 | 7 | use thiserror::Error; 8 | 9 | use crate::entity::storage::{Dispatch, Key, Storage}; 10 | 11 | #[derive(Debug, Eq, Error, PartialEq)] 12 | pub enum EntityError { 13 | #[error("required entity not found")] 14 | EntityNotFound, 15 | #[error("data operation failed")] 16 | Data, 17 | } 18 | 19 | pub trait Entity: Sized { 20 | type Key: Key; 21 | type Storage: Default + Dispatch + Storage; 22 | } 23 | 24 | pub trait Payload: Entity { 25 | type Data; 26 | 27 | fn get(&self) -> &Self::Data; 28 | 29 | fn get_mut(&mut self) -> &mut Self::Data; 30 | } 31 | -------------------------------------------------------------------------------- /plexus/src/entity/storage/hash.rs: -------------------------------------------------------------------------------- 1 | use ahash::AHashMap; 2 | use std::hash::Hash; 3 | use std::marker::PhantomData; 4 | 5 | use crate::entity::storage::{ 6 | AsStorage, AsStorageMut, DependentStorage, Dispatch, Dynamic, Enumerate, Get, IncrementalKeyer, 7 | IndependentStorage, InnerKey, Insert, InsertWithKey, Key, Keyer, Mode, Remove, Static, 8 | StorageTarget, 9 | }; 10 | use crate::entity::{Entity, Payload}; 11 | 12 | // TODO: The `Keyer` parameter `R` of `HashStorage` cannot be parameterized when 13 | // implementing the `AsStorage` and `Dispatch` traits even if the 14 | // conflicting implementations use a private local type or the `Keyer` 15 | // trait is private. Instead, this is implemented more specifically for 16 | // `IncrementalKeyer`. Perhaps this will be possible in the future. 17 | // 18 | // See https://github.com/rust-lang/rust/issues/48869 19 | 20 | pub struct HashStorage 21 | where 22 | E: Entity, 23 | R: Default, 24 | P: Mode, 25 | { 26 | inner: AHashMap::Key>, E>, 27 | keyer: R, 28 | phantom: PhantomData P>, 29 | } 30 | 31 | impl HashStorage 32 | where 33 | E: Entity, 34 | InnerKey: Eq + Hash, 35 | R: Default, 36 | P: Mode, 37 | { 38 | pub fn shrink_to_fit(&mut self) { 39 | self.inner.shrink_to_fit(); 40 | } 41 | } 42 | 43 | impl AsStorage for HashStorage 44 | where 45 | E: Entity, 46 | InnerKey: Eq + Hash, 47 | { 48 | fn as_storage(&self) -> &StorageTarget { 49 | self 50 | } 51 | } 52 | 53 | impl AsStorage for HashStorage 54 | where 55 | E: Entity, 56 | E::Key: Key, 57 | { 58 | fn as_storage(&self) -> &StorageTarget { 59 | self 60 | } 61 | } 62 | 63 | impl AsStorage for HashStorage 64 | where 65 | E: Entity, 66 | InnerKey: Eq + Hash, 67 | R: 'static + Default, 68 | { 69 | fn as_storage(&self) -> &StorageTarget { 70 | self 71 | } 72 | } 73 | 74 | impl AsStorageMut for HashStorage 75 | where 76 | E: Entity, 77 | InnerKey: Eq + Hash, 78 | { 79 | fn as_storage_mut(&mut self) -> &mut StorageTarget { 80 | self 81 | } 82 | } 83 | 84 | impl AsStorageMut for HashStorage 85 | where 86 | E: Entity, 87 | E::Key: Key, 88 | { 89 | fn as_storage_mut(&mut self) -> &mut StorageTarget { 90 | self 91 | } 92 | } 93 | 94 | impl AsStorageMut for HashStorage 95 | where 96 | E: Entity, 97 | InnerKey: Eq + Hash, 98 | R: 'static + Default, 99 | { 100 | fn as_storage_mut(&mut self) -> &mut StorageTarget { 101 | self 102 | } 103 | } 104 | 105 | impl Default for HashStorage 106 | where 107 | E: Entity, 108 | R: Default, 109 | P: Mode, 110 | { 111 | fn default() -> Self { 112 | HashStorage { 113 | inner: Default::default(), 114 | keyer: Default::default(), 115 | phantom: PhantomData, 116 | } 117 | } 118 | } 119 | 120 | #[rustfmt::skip] 121 | impl Dispatch for HashStorage 122 | where 123 | E: Entity, 124 | InnerKey: Eq + Hash, 125 | { 126 | type Target<'a> = dyn 'a + DependentStorage where E: 'a; 127 | } 128 | 129 | #[rustfmt::skip] 130 | impl Dispatch for HashStorage 131 | where 132 | E: Entity, 133 | E::Key: Key, 134 | { 135 | type Target<'a> = dyn 'a + IndependentStorage where E: 'a; 136 | } 137 | 138 | #[rustfmt::skip] 139 | impl Dispatch for HashStorage 140 | where 141 | E: Entity, 142 | InnerKey: Eq + Hash, 143 | R: 'static + Default, 144 | { 145 | type Target<'a> = Self where E: 'a; 146 | } 147 | 148 | impl Enumerate for HashStorage 149 | where 150 | E: Entity, 151 | InnerKey: Eq + Hash, 152 | R: Default, 153 | P: Mode, 154 | { 155 | fn len(&self) -> usize { 156 | self.inner.len() 157 | } 158 | 159 | fn iter(&self) -> Box> { 160 | Box::new( 161 | self.inner 162 | .iter() 163 | .map(|(key, entity)| (E::Key::from_inner(*key), entity)), 164 | ) 165 | } 166 | 167 | fn iter_mut(&mut self) -> Box> 168 | where 169 | E: Payload, 170 | { 171 | Box::new( 172 | self.inner 173 | .iter_mut() 174 | .map(|(key, entity)| (E::Key::from_inner(*key), entity.get_mut())), 175 | ) 176 | } 177 | } 178 | 179 | impl Get for HashStorage 180 | where 181 | E: Entity, 182 | InnerKey: Eq + Hash, 183 | R: Default, 184 | P: Mode, 185 | { 186 | fn get(&self, key: &E::Key) -> Option<&E> { 187 | self.inner.get(&key.into_inner()) 188 | } 189 | 190 | fn get_mut(&mut self, key: &E::Key) -> Option<&mut E> { 191 | self.inner.get_mut(&key.into_inner()) 192 | } 193 | } 194 | 195 | impl Insert for HashStorage 196 | where 197 | E: Entity, 198 | InnerKey: Eq + Hash, 199 | R: Keyer, 200 | P: Mode, 201 | { 202 | fn insert(&mut self, entity: E) -> E::Key { 203 | let key = self.keyer.next(); 204 | self.inner.insert(key, entity); 205 | Key::from_inner(key) 206 | } 207 | } 208 | 209 | impl InsertWithKey for HashStorage 210 | where 211 | E: Entity, 212 | InnerKey: Eq + Hash, 213 | P: Mode, 214 | { 215 | fn insert_with_key(&mut self, key: &E::Key, entity: E) -> Option { 216 | self.inner.insert(key.into_inner(), entity) 217 | } 218 | } 219 | 220 | impl Remove for HashStorage 221 | where 222 | E: Entity, 223 | InnerKey: Eq + Hash, 224 | R: Default, 225 | P: Mode, 226 | { 227 | fn remove(&mut self, key: &E::Key) -> Option { 228 | self.inner.remove(&key.into_inner()) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /plexus/src/entity/storage/mod.rs: -------------------------------------------------------------------------------- 1 | mod hash; 2 | 3 | use std::hash::Hash; 4 | 5 | use crate::entity::{Entity, Payload}; 6 | 7 | pub use crate::entity::storage::hash::HashStorage; 8 | 9 | pub mod prelude { 10 | pub use crate::entity::storage::{Enumerate, Get, Insert, InsertWithKey, Remove}; 11 | } 12 | 13 | pub type StorageTarget<'a, E> = <::Storage as Dispatch>::Target<'a>; 14 | 15 | pub type InnerKey = ::Inner; 16 | 17 | pub trait Key: Copy + Eq + Hash + Sized { 18 | type Inner: Copy + Sized; 19 | 20 | fn from_inner(key: Self::Inner) -> Self; 21 | 22 | fn into_inner(self) -> Self::Inner; 23 | } 24 | 25 | pub trait Keyer: Clone + Default 26 | where 27 | K: Key, 28 | { 29 | fn next(&mut self) -> K::Inner; 30 | } 31 | 32 | #[derive(Clone, Copy, Default)] 33 | pub struct IncrementalKeyer { 34 | key: u64, 35 | } 36 | 37 | impl Keyer for IncrementalKeyer 38 | where 39 | K: Key, 40 | { 41 | fn next(&mut self) -> K::Inner { 42 | let key = self.key; 43 | self.key = self.key.checked_add(1).expect("keyspace exhausted"); 44 | key 45 | } 46 | } 47 | 48 | #[rustfmt::skip] 49 | pub trait Dispatch 50 | where 51 | E: Entity, 52 | { 53 | type Target<'a>: 'a + ?Sized + Storage 54 | where 55 | E: 'a; 56 | } 57 | 58 | pub trait Mode {} 59 | 60 | pub enum Static {} 61 | 62 | impl Mode for Static {} 63 | 64 | pub enum Dynamic {} 65 | 66 | impl Mode for Dynamic {} 67 | 68 | // TODO: GATs cannot yet be used here while still supporting trait objects, 69 | // because the types cannot be bound in a supertrait. To support trait 70 | // objects, the associated types must be bound (likely to `Box`). 71 | // See https://github.com/rust-lang/rust/issues/67510 72 | pub trait Enumerate 73 | where 74 | E: Entity, 75 | { 76 | fn len(&self) -> usize; 77 | 78 | fn iter(&self) -> Box>; 79 | 80 | fn iter_mut(&mut self) -> Box> 81 | where 82 | E: Payload; 83 | } 84 | 85 | pub trait Get 86 | where 87 | E: Entity, 88 | { 89 | fn get(&self, key: &E::Key) -> Option<&E>; 90 | 91 | fn get_mut(&mut self, key: &E::Key) -> Option<&mut E>; 92 | 93 | fn contains_key(&self, key: &E::Key) -> bool { 94 | self.get(key).is_some() 95 | } 96 | } 97 | 98 | pub trait Insert 99 | where 100 | E: Entity, 101 | { 102 | fn insert(&mut self, entity: E) -> E::Key; 103 | } 104 | 105 | pub trait InsertWithKey 106 | where 107 | E: Entity, 108 | { 109 | fn insert_with_key(&mut self, key: &E::Key, entity: E) -> Option; 110 | } 111 | 112 | pub trait Remove 113 | where 114 | E: Entity, 115 | { 116 | fn remove(&mut self, key: &E::Key) -> Option; 117 | } 118 | 119 | pub trait Storage: AsStorage + AsStorageMut + Enumerate + Get + Remove 120 | where 121 | E: Entity, 122 | { 123 | } 124 | 125 | impl Storage for T 126 | where 127 | T: AsStorage + AsStorageMut + Enumerate + Get + Remove, 128 | E: Entity, 129 | { 130 | } 131 | 132 | pub trait DependentStorage: InsertWithKey + Storage 133 | where 134 | E: Entity, 135 | { 136 | } 137 | 138 | impl DependentStorage for T 139 | where 140 | T: InsertWithKey + Storage, 141 | E: Entity, 142 | { 143 | } 144 | 145 | pub trait IndependentStorage: Insert + Storage 146 | where 147 | E: Entity, 148 | { 149 | } 150 | 151 | impl IndependentStorage for T 152 | where 153 | T: Insert + Storage, 154 | E: Entity, 155 | { 156 | } 157 | 158 | pub trait Fuse 159 | where 160 | M: AsStorage, 161 | T: Entity, 162 | { 163 | type Output: AsStorage; 164 | 165 | fn fuse(self, source: M) -> Self::Output; 166 | } 167 | 168 | // TODO: It may not be possible for storage wrapper types to implement this 169 | // generally for all underlying storage types when using trait objects. An 170 | // input type parameter for the wrapped storage type may be required with 171 | // an implementation for each type that can be wrapped. Is there some way 172 | // to support blanket implementations? 173 | pub trait AsStorage 174 | where 175 | E: Entity, 176 | { 177 | fn as_storage(&self) -> &StorageTarget; 178 | } 179 | 180 | impl AsStorage for &'_ T 181 | where 182 | E: Entity, 183 | T: AsStorage + ?Sized, 184 | { 185 | fn as_storage(&self) -> &StorageTarget { 186 | >::as_storage(self) 187 | } 188 | } 189 | 190 | impl AsStorage for &'_ mut T 191 | where 192 | E: Entity, 193 | T: AsStorage + ?Sized, 194 | { 195 | fn as_storage(&self) -> &StorageTarget { 196 | >::as_storage(self) 197 | } 198 | } 199 | 200 | pub trait AsStorageMut: AsStorage 201 | where 202 | E: Entity, 203 | { 204 | fn as_storage_mut(&mut self) -> &mut StorageTarget; 205 | } 206 | 207 | impl AsStorageMut for &'_ mut T 208 | where 209 | E: Entity, 210 | T: AsStorageMut + ?Sized, 211 | { 212 | fn as_storage_mut(&mut self) -> &mut StorageTarget { 213 | >::as_storage_mut(self) 214 | } 215 | } 216 | 217 | pub trait AsStorageOf { 218 | fn as_storage_of(&self) -> &StorageTarget 219 | where 220 | E: Entity, 221 | Self: AsStorage, 222 | { 223 | self.as_storage() 224 | } 225 | 226 | fn as_storage_mut_of(&mut self) -> &mut StorageTarget 227 | where 228 | E: Entity, 229 | Self: AsStorageMut, 230 | { 231 | self.as_storage_mut() 232 | } 233 | } 234 | 235 | impl AsStorageOf for T {} 236 | -------------------------------------------------------------------------------- /plexus/src/entity/traverse.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | use std::hash::Hash; 3 | use std::marker::PhantomData; 4 | 5 | use crate::entity::borrow::Reborrow; 6 | use crate::entity::storage::{AsStorage, Enumerate}; 7 | use crate::entity::view::{Bind, ClosedView, Unbind}; 8 | 9 | pub enum Breadth {} 10 | pub enum Depth {} 11 | 12 | pub trait Buffer: Default + Extend 13 | where 14 | R: Order, 15 | { 16 | fn push(&mut self, item: T); 17 | fn pop(&mut self) -> Option; 18 | } 19 | 20 | impl Buffer for Vec { 21 | fn push(&mut self, item: T) { 22 | Vec::::push(self, item) 23 | } 24 | 25 | fn pop(&mut self) -> Option { 26 | Vec::::pop(self) 27 | } 28 | } 29 | 30 | impl Buffer for VecDeque { 31 | fn push(&mut self, item: T) { 32 | VecDeque::::push_back(self, item) 33 | } 34 | 35 | fn pop(&mut self) -> Option { 36 | VecDeque::::pop_front(self) 37 | } 38 | } 39 | 40 | /// Traversal ordering. 41 | /// 42 | /// Provides a default type implementing [`Buffer`] for the ordering described 43 | /// by `Self`. This reduces the number of required type parameters in types 44 | /// implementing traversals, as only an ordering type is needed to derive the 45 | /// buffer. Note that the item type can typically be derived from other required 46 | /// type parameters. 47 | /// 48 | /// See [`Traversal`]. 49 | /// 50 | /// [`Buffer`]: crate::entity::traverse::Buffer 51 | /// [`Traversal`]: crate::entity::traverse::Traversal 52 | pub trait Order: Sized { 53 | type Buffer: Buffer; 54 | } 55 | 56 | impl Order for Breadth { 57 | type Buffer = VecDeque; 58 | } 59 | 60 | impl Order for Depth { 61 | type Buffer = Vec; 62 | } 63 | 64 | pub trait Adjacency: ClosedView { 65 | type Output: IntoIterator; 66 | 67 | fn adjacency(&self) -> Self::Output; 68 | } 69 | 70 | #[derive(Debug)] 71 | pub struct Traversal 72 | where 73 | B: Reborrow, 74 | B::Target: AsStorage, 75 | T: Adjacency, 76 | R: Order, 77 | { 78 | storage: B, 79 | breadcrumbs: HashSet, 80 | buffer: R::Buffer, 81 | phantom: PhantomData T>, 82 | } 83 | 84 | impl Clone for Traversal 85 | where 86 | B: Clone + Reborrow, 87 | B::Target: AsStorage, 88 | T: Adjacency, 89 | R: Order, 90 | R::Buffer: Clone, 91 | { 92 | fn clone(&self) -> Self { 93 | Traversal { 94 | storage: self.storage.clone(), 95 | breadcrumbs: self.breadcrumbs.clone(), 96 | buffer: self.buffer.clone(), 97 | phantom: PhantomData, 98 | } 99 | } 100 | } 101 | 102 | impl From for Traversal 103 | where 104 | B: Reborrow, 105 | B::Target: AsStorage, 106 | T: Adjacency + Unbind, 107 | R: Order, 108 | { 109 | fn from(view: T) -> Self { 110 | let (storage, key) = view.unbind(); 111 | let capacity = storage.reborrow().as_storage().len(); 112 | let mut buffer = R::Buffer::default(); 113 | buffer.push(key); 114 | Traversal { 115 | storage, 116 | breadcrumbs: HashSet::with_capacity(capacity), 117 | buffer, 118 | phantom: PhantomData, 119 | } 120 | } 121 | } 122 | 123 | impl<'a, M, T, R> Iterator for Traversal<&'a M, T, R> 124 | where 125 | M: 'a + AsStorage, 126 | T: Adjacency + Bind<&'a M>, 127 | R: Order, 128 | { 129 | type Item = T; 130 | 131 | fn next(&mut self) -> Option { 132 | while let Some(view) = self.buffer.pop().and_then(|key| T::bind(self.storage, key)) { 133 | if self.breadcrumbs.insert(view.key()) { 134 | self.buffer.extend(view.adjacency()); 135 | return Some(view); 136 | } 137 | } 138 | None 139 | } 140 | 141 | fn size_hint(&self) -> (usize, Option) { 142 | ( 143 | 1, 144 | Some(AsStorage::::as_storage(&self.storage).len()), 145 | ) 146 | } 147 | } 148 | 149 | /// Trace of a traversal, iteration, etc. 150 | /// 151 | /// A trace caches _breadcrumbs_, which identify entities encountered during a 152 | /// traversal. 153 | pub trait Trace { 154 | /// Inserts the given breadcrumb into the trace. The breadcrumb may or may 155 | /// not be cached. 156 | /// 157 | /// A _collision_ occurs if a breadcrumb that has been cached by the trace 158 | /// is reinserted. If a collision with the trace is detected, then this 159 | /// function returns `false` and otherwise returns `true` (similarly to 160 | /// collections like [`HashSet`]). 161 | /// 162 | /// If `false` is returned, then any traversal or iteration should 163 | /// terminate. 164 | /// 165 | /// [`HashSet`]: std::collections::HashSet 166 | fn insert(&mut self, breadcrumb: T) -> bool; 167 | } 168 | 169 | /// Trace that caches and detects collisions with only the first breadcrumb that 170 | /// is inserted. 171 | /// 172 | /// A collision only occurs if the first breadcrumb is reinserted; no other 173 | /// breadcrumbs are cached. 174 | /// 175 | /// This trace should **not** be used when traversing a structure with unknown 176 | /// consistency, because it may never signal that the iteration should 177 | /// terminate. However, it requires very little space and time to operate. 178 | #[derive(Clone, Copy, Debug, Default)] 179 | pub struct TraceFirst 180 | where 181 | T: Copy, 182 | { 183 | breadcrumb: Option, 184 | } 185 | 186 | impl Trace for TraceFirst 187 | where 188 | T: Copy + Eq, 189 | { 190 | fn insert(&mut self, breadcrumb: T) -> bool { 191 | match self.breadcrumb { 192 | Some(collision) => collision != breadcrumb, 193 | None => { 194 | self.breadcrumb = Some(breadcrumb); 195 | true 196 | } 197 | } 198 | } 199 | } 200 | 201 | /// Trace that caches all inserted breadcrumbs and detects collisions with any 202 | /// such breadcrumb. 203 | /// 204 | /// This trace is very robust and reliably signals termination of a traversal, 205 | /// but requires non-trivial space to cache breadcrumbs and must hash 206 | /// breadcrumbs to detect collisions. 207 | #[derive(Clone, Debug, Default)] 208 | pub struct TraceAny 209 | where 210 | T: Copy + Eq + Hash, 211 | { 212 | breadcrumbs: HashSet, 213 | } 214 | 215 | impl Trace for TraceAny 216 | where 217 | T: Copy + Eq + Hash, 218 | { 219 | fn insert(&mut self, breadcrumb: T) -> bool { 220 | self.breadcrumbs.insert(breadcrumb) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /plexus/src/entity/view.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{Hash, Hasher}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | use crate::entity::borrow::{Reborrow, ReborrowInto, ReborrowMut}; 5 | use crate::entity::storage::{AsStorage, AsStorageMut, Get, Key}; 6 | use crate::entity::{Entity, Payload}; 7 | 8 | pub trait ClosedView { 9 | type Key: Key; 10 | type Entity: Entity; 11 | 12 | fn key(&self) -> Self::Key; 13 | } 14 | 15 | pub trait Bind: ClosedView + Sized 16 | where 17 | B: Reborrow, 18 | { 19 | fn bind(storage: B, key: Self::Key) -> Option; 20 | } 21 | 22 | // Note that orphan views do not gain this implementation without a 23 | // `From>` implementation. 24 | impl Bind for T 25 | where 26 | B: Reborrow, 27 | B::Target: AsStorage, 28 | T: ClosedView + From::Entity>> + Sized, 29 | { 30 | fn bind(storage: B, key: Self::Key) -> Option { 31 | View::bind(storage, key).map(Self::from) 32 | } 33 | } 34 | 35 | pub trait Rebind: ClosedView 36 | where 37 | B: Reborrow, 38 | T: ClosedView, 39 | { 40 | fn rebind(self, key: T::Key) -> Option; 41 | } 42 | 43 | impl Rebind for U 44 | where 45 | B: Reborrow, 46 | B::Target: AsStorage + AsStorage, 47 | T: ClosedView + From::Entity>>, 48 | U: ClosedView + Into::Entity>>, 49 | { 50 | fn rebind(self, key: T::Key) -> Option { 51 | self.into().rebind(key).map(T::from) 52 | } 53 | } 54 | 55 | pub trait Unbind: ClosedView 56 | where 57 | B: Reborrow, 58 | { 59 | fn unbind(self) -> (B, Self::Key); 60 | } 61 | 62 | impl Unbind for T 63 | where 64 | B: Reborrow, 65 | B::Target: AsStorage, 66 | T: ClosedView + Into::Entity>>, 67 | { 68 | fn unbind(self) -> (B, Self::Key) { 69 | self.into().unbind() 70 | } 71 | } 72 | 73 | pub struct View 74 | where 75 | B: Reborrow, 76 | B::Target: AsStorage, 77 | E: Entity, 78 | { 79 | storage: B, 80 | key: E::Key, 81 | } 82 | 83 | impl View 84 | where 85 | B: Reborrow, 86 | B::Target: AsStorage, 87 | E: Entity, 88 | { 89 | pub fn bind(storage: B, key: E::Key) -> Option { 90 | storage 91 | .reborrow() 92 | .as_storage() 93 | .contains_key(&key) 94 | .then(|| View::bind_unchecked(storage, key)) 95 | } 96 | 97 | pub fn bind_unchecked(storage: B, key: E::Key) -> Self { 98 | View { storage, key } 99 | } 100 | 101 | pub fn bind_into(storage: B, key: E::Key) -> Option 102 | where 103 | T: From, 104 | { 105 | View::bind(storage, key).map(T::from) 106 | } 107 | 108 | pub fn unbind(self) -> (B, E::Key) { 109 | let View { storage, key, .. } = self; 110 | (storage, key) 111 | } 112 | 113 | pub fn rebind(self, key: T::Key) -> Option> 114 | where 115 | B::Target: AsStorage, 116 | T: Entity, 117 | { 118 | let (storage, _) = self.unbind(); 119 | View::bind(storage, key) 120 | } 121 | 122 | pub fn rebind_into(self, key: U::Key) -> Option 123 | where 124 | B::Target: AsStorage, 125 | T: From>, 126 | U: Entity, 127 | { 128 | self.rebind(key).map(T::from) 129 | } 130 | 131 | pub fn get(&self) -> &E::Data 132 | where 133 | E: Payload, 134 | { 135 | self.as_entity().get() 136 | } 137 | 138 | pub fn key(&self) -> E::Key { 139 | self.key 140 | } 141 | 142 | pub fn to_ref(&self) -> View<&B::Target, E> { 143 | View::bind_unchecked(self.storage.reborrow(), self.key) 144 | } 145 | 146 | fn as_entity(&self) -> &E { 147 | self.storage 148 | .reborrow() 149 | .as_storage() 150 | .get(&self.key) 151 | .expect("view key invalidated") 152 | } 153 | } 154 | 155 | impl View 156 | where 157 | B: ReborrowMut, 158 | B::Target: AsStorage, 159 | E: Entity, 160 | { 161 | /// Mutably reborrows the interior of the view. 162 | /// 163 | /// It is possible to invalidate views using this function. Care must be taken to ensure that 164 | /// the originating view's key is still present in storage after the reborrowed view is 165 | /// dropped. 166 | // LINT: The "unannotated" name of this function is `to_mut`, but "unchecked" is used to 167 | // indicate that this function can be used incorrectly and corrupt views. `to_mut` does 168 | // not violate lints, but `to_mut_unchecked` does. This function and its proxies allow 169 | // this unconventional name. 170 | #[expect(clippy::wrong_self_convention)] 171 | pub fn to_mut_unchecked(&mut self) -> View<&mut B::Target, E> { 172 | View::bind_unchecked(self.storage.reborrow_mut(), self.key) 173 | } 174 | } 175 | 176 | impl View 177 | where 178 | B: ReborrowMut, 179 | B::Target: AsStorageMut, 180 | E: Entity, 181 | { 182 | pub fn get_mut(&mut self) -> &mut E::Data 183 | where 184 | E: Payload, 185 | { 186 | self.as_entity_mut().get_mut() 187 | } 188 | 189 | fn as_entity_mut(&mut self) -> &mut E { 190 | self.storage 191 | .reborrow_mut() 192 | .as_storage_mut() 193 | .get_mut(&self.key) 194 | .expect("view key invalidated") 195 | } 196 | } 197 | 198 | impl<'a, B, E> View 199 | where 200 | B: ReborrowInto<'a>, 201 | B::Target: AsStorage, 202 | E: Entity, 203 | { 204 | pub fn into_ref(self) -> View<&'a B::Target, E> { 205 | let (storage, key) = self.unbind(); 206 | View::bind_unchecked(storage.reborrow_into(), key) 207 | } 208 | } 209 | 210 | impl AsRef for View 211 | where 212 | B: Reborrow, 213 | B::Target: AsStorage, 214 | E: Entity, 215 | { 216 | fn as_ref(&self) -> &E::Key { 217 | &self.key 218 | } 219 | } 220 | 221 | impl Clone for View 222 | where 223 | B: Clone + Reborrow, 224 | B::Target: AsStorage, 225 | E: Entity, 226 | { 227 | fn clone(&self) -> Self { 228 | View { 229 | storage: self.storage.clone(), 230 | key: self.key, 231 | } 232 | } 233 | } 234 | 235 | impl ClosedView for View 236 | where 237 | B: Reborrow, 238 | B::Target: AsStorage, 239 | E: Entity, 240 | { 241 | type Key = E::Key; 242 | type Entity = E; 243 | 244 | fn key(&self) -> Self::Key { 245 | self.key 246 | } 247 | } 248 | 249 | impl Copy for View 250 | where 251 | B: Copy + Reborrow, 252 | B::Target: AsStorage, 253 | E: Entity, 254 | { 255 | } 256 | 257 | impl Deref for View 258 | where 259 | B: Reborrow, 260 | B::Target: AsStorage, 261 | E: Entity, 262 | { 263 | type Target = E; 264 | 265 | fn deref(&self) -> &Self::Target { 266 | self.as_entity() 267 | } 268 | } 269 | 270 | impl DerefMut for View 271 | where 272 | B: ReborrowMut, 273 | B::Target: AsStorageMut, 274 | E: Entity, 275 | { 276 | fn deref_mut(&mut self) -> &mut Self::Target { 277 | self.as_entity_mut() 278 | } 279 | } 280 | 281 | impl Eq for View 282 | where 283 | B: Reborrow, 284 | B::Target: AsStorage, 285 | E: Entity, 286 | { 287 | } 288 | 289 | impl Hash for View 290 | where 291 | B: Reborrow, 292 | B::Target: AsStorage, 293 | E: Entity, 294 | { 295 | fn hash(&self, state: &mut H) 296 | where 297 | H: Hasher, 298 | { 299 | self.key.hash(state); 300 | } 301 | } 302 | 303 | impl PartialEq for View 304 | where 305 | B: Reborrow, 306 | B::Target: AsStorage, 307 | E: Entity, 308 | { 309 | fn eq(&self, other: &Self) -> bool { 310 | self.key == other.key 311 | } 312 | } 313 | 314 | pub struct Orphan<'a, E> 315 | where 316 | E: Payload, 317 | { 318 | data: &'a mut E::Data, 319 | key: E::Key, 320 | } 321 | 322 | impl<'a, E> Orphan<'a, E> 323 | where 324 | E: Payload, 325 | E::Data: 'a, 326 | { 327 | pub fn bind(storage: &'a mut M, key: E::Key) -> Option 328 | where 329 | E: 'a, 330 | M: AsStorageMut, 331 | { 332 | View::bind(storage, key).map(Orphan::from) 333 | } 334 | 335 | pub fn bind_unchecked(data: &'a mut E::Data, key: E::Key) -> Self { 336 | Orphan { data, key } 337 | } 338 | 339 | pub fn bind_into(storage: &'a mut M, key: E::Key) -> Option 340 | where 341 | E: 'a, 342 | T: From, 343 | M: AsStorageMut, 344 | { 345 | Orphan::bind(storage, key).map(T::from) 346 | } 347 | 348 | pub fn get(&self) -> &E::Data { 349 | &*self.data 350 | } 351 | 352 | pub fn get_mut(&mut self) -> &mut E::Data { 353 | self.data 354 | } 355 | 356 | pub fn key(&self) -> E::Key { 357 | self.key 358 | } 359 | } 360 | 361 | impl<'a, E> AsRef for Orphan<'a, E> 362 | where 363 | E: Payload, 364 | E::Data: 'a, 365 | { 366 | fn as_ref(&self) -> &E::Key { 367 | &self.key 368 | } 369 | } 370 | 371 | impl<'a, E> ClosedView for Orphan<'a, E> 372 | where 373 | E: Payload, 374 | E::Data: 'a, 375 | { 376 | type Key = E::Key; 377 | type Entity = E; 378 | 379 | fn key(&self) -> Self::Key { 380 | self.key 381 | } 382 | } 383 | 384 | impl<'a, E, M> From> for Orphan<'a, E> 385 | where 386 | E: 'a + Payload, 387 | E::Data: 'a, 388 | M: AsStorageMut, 389 | { 390 | fn from(view: View<&'a mut M, E>) -> Self { 391 | let (storage, key) = view.unbind(); 392 | let entity = storage 393 | .as_storage_mut() 394 | .get_mut(&key) 395 | .expect("view key invalidated"); 396 | Orphan::bind_unchecked(entity.get_mut(), key) 397 | } 398 | } 399 | 400 | impl<'a, E> Eq for Orphan<'a, E> 401 | where 402 | E: Payload, 403 | E::Data: 'a, 404 | { 405 | } 406 | 407 | impl<'a, E> Hash for Orphan<'a, E> 408 | where 409 | E: Payload, 410 | E::Data: 'a, 411 | { 412 | fn hash(&self, state: &mut H) 413 | where 414 | H: Hasher, 415 | { 416 | self.key.hash(state); 417 | } 418 | } 419 | 420 | impl<'a, E> PartialEq for Orphan<'a, E> 421 | where 422 | E: Payload, 423 | E::Data: 'a, 424 | { 425 | fn eq(&self, other: &Self) -> bool { 426 | self.key == other.key 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /plexus/src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | //! Geometric traits and computational geometry. 2 | //! 3 | //! Plexus uses the [`theon`] crate to abstract over types that represent Euclidean spaces and 4 | //! implement linear algebra. Types and traits are re-exported from [`theon`] in this module, but 5 | //! it may be necessary to import additional types from [`theon`]. 6 | //! 7 | //! [`theon`]: https://crates.io/crates/theon 8 | 9 | use num::{One, Zero}; 10 | 11 | pub mod partition; 12 | 13 | pub use theon::query::*; 14 | pub use theon::space::{Scalar, Vector}; 15 | pub use theon::{AsPosition, AsPositionMut, Position}; 16 | 17 | pub trait FromGeometry { 18 | fn from_geometry(other: T) -> Self; 19 | } 20 | 21 | impl FromGeometry for T { 22 | fn from_geometry(other: T) -> Self { 23 | other 24 | } 25 | } 26 | 27 | /// Geometry elision into `()`. 28 | impl FromGeometry for () 29 | where 30 | T: UnitGeometry, 31 | { 32 | fn from_geometry(_: T) -> Self {} 33 | } 34 | 35 | /// Geometry elision from `()`. 36 | impl FromGeometry<()> for T 37 | where 38 | T: UnitGeometry + Default, 39 | { 40 | fn from_geometry(_: ()) -> Self { 41 | T::default() 42 | } 43 | } 44 | 45 | /// Geometry elision. 46 | /// 47 | /// Geometric types that implement this trait may be elided. In particular, 48 | /// these types may be converted into and from `()` via the [`FromGeometry`] and 49 | /// [`IntoGeometry`] traits. 50 | /// 51 | /// For a geometric type `T`, the following table illustrates the elisions in 52 | /// which `T` may participate: 53 | /// 54 | /// | Bounds on `T` | From | Into | 55 | /// |--------------------------|------|------| 56 | /// | `UnitGeometry` | `T` | `()` | 57 | /// | `Default + UnitGeometry` | `()` | `T` | 58 | /// 59 | /// These conversions are useful when converting between mesh data structures 60 | /// with incompatible geometry, such as from a [`MeshGraph`] with face geometry 61 | /// to a [`MeshBuffer`] that cannot support such geometry. 62 | /// 63 | /// When geometry features are enabled, `UnitGeometry` is implemented for 64 | /// integrated foreign types. 65 | /// 66 | /// [`FromGeometry`]: crate::geometry::FromGeometry 67 | /// [`IntoGeometry`]: crate::geometry::IntoGeometry 68 | /// [`MeshBuffer`]: crate::buffer::MeshBuffer 69 | /// [`MeshGraph`]: crate::graph::MeshGraph 70 | pub trait UnitGeometry {} 71 | 72 | pub trait IntoGeometry { 73 | fn into_geometry(self) -> T; 74 | } 75 | 76 | impl IntoGeometry for T 77 | where 78 | U: FromGeometry, 79 | { 80 | fn into_geometry(self) -> U { 81 | U::from_geometry(self) 82 | } 83 | } 84 | 85 | pub trait Metric: Eq + One + Ord + Zero {} 86 | 87 | impl Metric for Q where Q: Eq + One + Ord + Zero {} 88 | -------------------------------------------------------------------------------- /plexus/src/geometry/partition.rs: -------------------------------------------------------------------------------- 1 | use approx::abs_diff_eq; 2 | use num::traits::real::Real; 3 | use num::Zero; 4 | use std::cmp::Ordering; 5 | use theon::query::{Line, Plane}; 6 | use theon::space::{EuclideanSpace, FiniteDimensional}; 7 | use typenum::{U1, U2, U3}; 8 | 9 | // "Left" and "right" are arbitrary here and refer to the partitioned spaces 10 | // formed by a geometric entity. This is a point, line, and plane in one, two, 11 | // three dimensions, respectively. 12 | #[derive(Clone, Copy, Eq, Hash, PartialEq)] 13 | pub enum BinaryPartition { 14 | Left, 15 | Right, 16 | } 17 | 18 | pub trait PointPartition 19 | where 20 | S: EuclideanSpace, 21 | { 22 | fn partition(&self, point: S) -> Option; 23 | } 24 | 25 | impl PointPartition for S 26 | where 27 | S: EuclideanSpace + FiniteDimensional, 28 | { 29 | // TODO: Should `EmptyOrd` be used here? 30 | fn partition(&self, point: S) -> Option { 31 | let ax = self.into_x(); 32 | let px = point.into_x(); 33 | match px.partial_cmp(&ax) { 34 | Some(Ordering::Less) => Some(BinaryPartition::Left), 35 | Some(Ordering::Greater) => Some(BinaryPartition::Right), 36 | _ => None, 37 | } 38 | } 39 | } 40 | 41 | impl PointPartition for Line 42 | where 43 | S: EuclideanSpace + FiniteDimensional, 44 | { 45 | fn partition(&self, point: S) -> Option { 46 | // Compute the determinant of a matrix composed of points along the line 47 | // and the queried point. This can also be thought of as a two- 48 | // dimensional cross product. 49 | // TODO: Perhaps this should be exposed by Theon instead. 50 | let (ax, ay) = self.origin.into_xy(); 51 | let (bx, by) = (self.origin + *self.direction.get()).into_xy(); 52 | let (px, py) = point.into_xy(); 53 | let determinant = ((bx - ax) * (py - ay)) - ((by - ay) * (px - ax)); 54 | if abs_diff_eq!(determinant, Zero::zero()) { 55 | None 56 | } 57 | else { 58 | Some(if determinant.is_sign_positive() { 59 | BinaryPartition::Left 60 | } 61 | else { 62 | BinaryPartition::Right 63 | }) 64 | } 65 | } 66 | } 67 | 68 | impl PointPartition for Plane 69 | where 70 | S: EuclideanSpace + FiniteDimensional, 71 | { 72 | fn partition(&self, point: S) -> Option { 73 | let _ = point; 74 | todo!() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plexus/src/graph/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::builder::{FacetBuilder, MeshBuilder, SurfaceBuilder}; 2 | use crate::geometry::{FromGeometry, IntoGeometry}; 3 | use crate::graph::data::GraphData; 4 | use crate::graph::face::FaceKey; 5 | use crate::graph::mutation::face::{self, FaceInsertCache}; 6 | use crate::graph::mutation::vertex; 7 | use crate::graph::mutation::{Immediate, Mutation}; 8 | use crate::graph::vertex::VertexKey; 9 | use crate::graph::{GraphError, MeshGraph}; 10 | use crate::transact::{ClosedInput, Transact}; 11 | 12 | pub struct GraphBuilder 13 | where 14 | G: GraphData, 15 | { 16 | mutation: Mutation>>, 17 | } 18 | 19 | impl Default for GraphBuilder 20 | where 21 | G: GraphData, 22 | { 23 | fn default() -> Self { 24 | GraphBuilder { 25 | mutation: Mutation::from(MeshGraph::default()), 26 | } 27 | } 28 | } 29 | 30 | impl ClosedInput for GraphBuilder 31 | where 32 | G: GraphData, 33 | { 34 | type Input = (); 35 | } 36 | 37 | impl MeshBuilder for GraphBuilder 38 | where 39 | G: GraphData, 40 | { 41 | type Builder = Self; 42 | 43 | type Vertex = G::Vertex; 44 | type Facet = G::Face; 45 | 46 | fn surface_with(&mut self, f: F) -> Result 47 | where 48 | Self::Error: From, 49 | F: FnOnce(&mut Self::Builder) -> Result, 50 | { 51 | f(self).map_err(|error| error.into()) 52 | } 53 | } 54 | 55 | impl Transact<::Input> for GraphBuilder 56 | where 57 | G: GraphData, 58 | { 59 | type Commit = MeshGraph; 60 | type Abort = (); 61 | type Error = GraphError; 62 | 63 | fn commit(self) -> Result { 64 | let GraphBuilder { mutation } = self; 65 | mutation.commit() 66 | } 67 | 68 | fn abort(self) -> Self::Abort {} 69 | } 70 | 71 | impl SurfaceBuilder for GraphBuilder 72 | where 73 | G: GraphData, 74 | { 75 | type Builder = Self; 76 | type Key = VertexKey; 77 | 78 | type Vertex = G::Vertex; 79 | type Facet = G::Face; 80 | 81 | fn facets_with(&mut self, f: F) -> Result 82 | where 83 | Self::Error: From, 84 | F: FnOnce(&mut Self::Builder) -> Result, 85 | { 86 | f(self).map_err(|error| error.into()) 87 | } 88 | 89 | fn insert_vertex(&mut self, data: T) -> Result 90 | where 91 | Self::Vertex: FromGeometry, 92 | { 93 | Ok(vertex::insert(&mut self.mutation, data.into_geometry())) 94 | } 95 | } 96 | 97 | impl FacetBuilder for GraphBuilder 98 | where 99 | G: GraphData, 100 | { 101 | type Facet = G::Face; 102 | type Key = FaceKey; 103 | 104 | fn insert_facet(&mut self, keys: T, data: U) -> Result 105 | where 106 | Self::Facet: FromGeometry, 107 | T: AsRef<[VertexKey]>, 108 | { 109 | let cache = FaceInsertCache::from_storage(&self.mutation, keys.as_ref())?; 110 | let data = data.into_geometry(); 111 | face::insert_with(&mut self.mutation, cache, || (Default::default(), data)) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /plexus/src/graph/core.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::entity::storage::{AsStorage, AsStorageMut, Fuse, StorageTarget}; 4 | use crate::entity::Entity; 5 | use crate::graph::data::{GraphData, Parametric}; 6 | use crate::graph::edge::{Arc, Edge}; 7 | use crate::graph::face::Face; 8 | use crate::graph::vertex::Vertex; 9 | 10 | /// A complete core that owns all of its storage. 11 | pub type OwnedCore = Core< 12 | G, 13 | as Entity>::Storage, 14 | as Entity>::Storage, 15 | as Entity>::Storage, 16 | as Entity>::Storage, 17 | >; 18 | 19 | /// Adaptable graph representation that can incorporate arbitrary storage. 20 | /// 21 | /// Cores act as a container for storage that comprises a graph and allow 22 | /// storage to be moved (_fused_ and _unfused_) as values or references. A core 23 | /// may or may not own its storage and may or may not provide storage for all 24 | /// entities. When a core does not own its storage, it is _ephemeral_. 25 | /// 26 | /// Cores are used by the mutation API to unfuse storage and guard it behind 27 | /// per-entity APIs. Unlike `MeshGraph`, `Core` does not implement the 28 | /// `Consistent` trait. `MeshGraph` contains a core, but does not mutate it 29 | /// outside of the mutation API, which maintains consistency. 30 | /// 31 | /// A core's fields may be _unfused_ and _fused_. When a field is unfused, its 32 | /// type is `()`. An unfused field has no value and is zero-sized. A fused field 33 | /// has any type other than `()`. These fields should provide storage for their 34 | /// corresponding entity. The `Fuse` trait is used to transition from `()` to 35 | /// some other type by _fusing_ storage into a `Core`. `Fuse` implementations 36 | /// enforce storage constraints; it is not possible to fuse values that do not 37 | /// expose storage to yet unfused entities. 38 | /// 39 | /// A `Core` with no unfused fields is _complete_. 40 | pub struct Core 41 | where 42 | G: GraphData, 43 | { 44 | pub(in crate::graph) vertices: V, 45 | pub(in crate::graph) arcs: A, 46 | pub(in crate::graph) edges: E, 47 | pub(in crate::graph) faces: F, 48 | phantom: PhantomData G>, 49 | } 50 | 51 | impl Core 52 | where 53 | G: GraphData, 54 | { 55 | pub fn empty() -> Self { 56 | Core { 57 | vertices: (), 58 | arcs: (), 59 | edges: (), 60 | faces: (), 61 | phantom: PhantomData, 62 | } 63 | } 64 | } 65 | 66 | impl Core 67 | where 68 | G: GraphData, 69 | { 70 | pub fn unfuse(self) -> (V, A, E, F) { 71 | let Core { 72 | vertices, 73 | arcs, 74 | edges, 75 | faces, 76 | .. 77 | } = self; 78 | (vertices, arcs, edges, faces) 79 | } 80 | } 81 | 82 | impl AsStorage> for Core 83 | where 84 | V: AsStorage>, 85 | G: GraphData, 86 | { 87 | fn as_storage(&self) -> &StorageTarget> { 88 | self.vertices.as_storage() 89 | } 90 | } 91 | 92 | impl AsStorage> for Core 93 | where 94 | A: AsStorage>, 95 | G: GraphData, 96 | { 97 | fn as_storage(&self) -> &StorageTarget> { 98 | self.arcs.as_storage() 99 | } 100 | } 101 | 102 | impl AsStorage> for Core 103 | where 104 | E: AsStorage>, 105 | G: GraphData, 106 | { 107 | fn as_storage(&self) -> &StorageTarget> { 108 | self.edges.as_storage() 109 | } 110 | } 111 | 112 | impl AsStorage> for Core 113 | where 114 | F: AsStorage>, 115 | G: GraphData, 116 | { 117 | fn as_storage(&self) -> &StorageTarget> { 118 | self.faces.as_storage() 119 | } 120 | } 121 | 122 | impl AsStorageMut> for Core 123 | where 124 | V: AsStorageMut>, 125 | G: GraphData, 126 | { 127 | fn as_storage_mut(&mut self) -> &mut StorageTarget> { 128 | self.vertices.as_storage_mut() 129 | } 130 | } 131 | 132 | impl AsStorageMut> for Core 133 | where 134 | A: AsStorageMut>, 135 | G: GraphData, 136 | { 137 | fn as_storage_mut(&mut self) -> &mut StorageTarget> { 138 | self.arcs.as_storage_mut() 139 | } 140 | } 141 | 142 | impl AsStorageMut> for Core 143 | where 144 | E: AsStorageMut>, 145 | G: GraphData, 146 | { 147 | fn as_storage_mut(&mut self) -> &mut StorageTarget> { 148 | self.edges.as_storage_mut() 149 | } 150 | } 151 | 152 | impl AsStorageMut> for Core 153 | where 154 | F: AsStorageMut>, 155 | G: GraphData, 156 | { 157 | fn as_storage_mut(&mut self) -> &mut StorageTarget> { 158 | self.faces.as_storage_mut() 159 | } 160 | } 161 | 162 | impl Default for Core 163 | where 164 | G: GraphData, 165 | V: Default, 166 | A: Default, 167 | E: Default, 168 | F: Default, 169 | { 170 | fn default() -> Self { 171 | Core { 172 | vertices: Default::default(), 173 | arcs: Default::default(), 174 | edges: Default::default(), 175 | faces: Default::default(), 176 | phantom: PhantomData, 177 | } 178 | } 179 | } 180 | 181 | impl Fuse> for Core 182 | where 183 | V: AsStorage>, 184 | G: GraphData, 185 | { 186 | type Output = Core; 187 | 188 | fn fuse(self, vertices: V) -> Self::Output { 189 | let Core { 190 | arcs, edges, faces, .. 191 | } = self; 192 | Core { 193 | vertices, 194 | arcs, 195 | edges, 196 | faces, 197 | phantom: PhantomData, 198 | } 199 | } 200 | } 201 | 202 | impl Fuse> for Core 203 | where 204 | A: AsStorage>, 205 | G: GraphData, 206 | { 207 | type Output = Core; 208 | 209 | fn fuse(self, arcs: A) -> Self::Output { 210 | let Core { 211 | vertices, 212 | edges, 213 | faces, 214 | .. 215 | } = self; 216 | Core { 217 | vertices, 218 | arcs, 219 | edges, 220 | faces, 221 | phantom: PhantomData, 222 | } 223 | } 224 | } 225 | 226 | impl Fuse> for Core 227 | where 228 | E: AsStorage>, 229 | G: GraphData, 230 | { 231 | type Output = Core; 232 | 233 | fn fuse(self, edges: E) -> Self::Output { 234 | let Core { 235 | vertices, 236 | arcs, 237 | faces, 238 | .. 239 | } = self; 240 | Core { 241 | vertices, 242 | arcs, 243 | edges, 244 | faces, 245 | phantom: PhantomData, 246 | } 247 | } 248 | } 249 | 250 | impl Fuse> for Core 251 | where 252 | F: AsStorage>, 253 | G: GraphData, 254 | { 255 | type Output = Core; 256 | 257 | fn fuse(self, faces: F) -> Self::Output { 258 | let Core { 259 | vertices, 260 | arcs, 261 | edges, 262 | .. 263 | } = self; 264 | Core { 265 | vertices, 266 | arcs, 267 | edges, 268 | faces, 269 | phantom: PhantomData, 270 | } 271 | } 272 | } 273 | 274 | impl Parametric for Core 275 | where 276 | G: GraphData, 277 | { 278 | type Data = G; 279 | } 280 | -------------------------------------------------------------------------------- /plexus/src/graph/data.rs: -------------------------------------------------------------------------------- 1 | use crate::entity::borrow::Reborrow; 2 | 3 | pub type Data = ::Data; 4 | 5 | /// Graph data. 6 | /// 7 | /// Specifies the types used to represent data in vertices, arcs, edges, and 8 | /// faces in a [`MeshGraph`]. Arbitrary types can be used, including the unit 9 | /// type `()` for no data at all. 10 | /// 11 | /// Geometric operations depend on understanding the positional data in vertices 12 | /// exposed by the [`AsPosition`] trait. If the `Vertex` type implements 13 | /// [`AsPosition`], then geometric operations supported by the `Position` type 14 | /// are exposed by graph APIs. 15 | /// 16 | /// # Examples 17 | /// 18 | /// ```rust 19 | /// # extern crate decorum; 20 | /// # extern crate nalgebra; 21 | /// # extern crate num; 22 | /// # extern crate plexus; 23 | /// # 24 | /// use decorum::R64; 25 | /// use nalgebra::{Point3, Vector4}; 26 | /// use num::Zero; 27 | /// use plexus::geometry::{AsPosition, IntoGeometry}; 28 | /// use plexus::graph::{GraphData, MeshGraph}; 29 | /// use plexus::prelude::*; 30 | /// use plexus::primitive::generate::Position; 31 | /// use plexus::primitive::sphere::UvSphere; 32 | /// 33 | /// #[derive(Clone, Copy, Eq, Hash, PartialEq)] 34 | /// pub struct Vertex { 35 | /// pub position: Point3, 36 | /// pub color: Vector4, 37 | /// } 38 | /// 39 | /// impl GraphData for Vertex { 40 | /// type Vertex = Self; 41 | /// type Arc = (); 42 | /// type Edge = (); 43 | /// type Face = (); 44 | /// } 45 | /// 46 | /// impl AsPosition for Vertex { 47 | /// type Position = Point3; 48 | /// 49 | /// fn as_position(&self) -> &Self::Position { 50 | /// &self.position 51 | /// } 52 | /// } 53 | /// 54 | /// // Create a mesh from a uv-sphere. 55 | /// let mut graph: MeshGraph = UvSphere::new(8, 8) 56 | /// .polygons::>>() 57 | /// .map_vertices(|position| Vertex { 58 | /// position, 59 | /// color: Zero::zero(), 60 | /// }) 61 | /// .collect(); 62 | /// ``` 63 | /// 64 | /// [`AsPosition`]: crate::geometry::AsPosition 65 | /// [`MeshGraph`]: crate::graph::MeshGraph 66 | pub trait GraphData: Sized { 67 | type Vertex: Clone; 68 | type Arc: Clone + Default; 69 | type Edge: Clone + Default; 70 | type Face: Clone + Default; 71 | } 72 | 73 | impl GraphData for () { 74 | type Vertex = (); 75 | type Arc = (); 76 | type Edge = (); 77 | type Face = (); 78 | } 79 | 80 | impl GraphData for (T, T) 81 | where 82 | T: Clone, 83 | { 84 | type Vertex = Self; 85 | type Arc = (); 86 | type Edge = (); 87 | type Face = (); 88 | } 89 | 90 | impl GraphData for (T, T, T) 91 | where 92 | T: Clone, 93 | { 94 | type Vertex = Self; 95 | type Arc = (); 96 | type Edge = (); 97 | type Face = (); 98 | } 99 | 100 | impl GraphData for [T; 2] 101 | where 102 | T: Clone, 103 | { 104 | type Vertex = Self; 105 | type Arc = (); 106 | type Edge = (); 107 | type Face = (); 108 | } 109 | 110 | impl GraphData for [T; 3] 111 | where 112 | T: Clone, 113 | { 114 | type Vertex = Self; 115 | type Arc = (); 116 | type Edge = (); 117 | type Face = (); 118 | } 119 | 120 | pub trait Parametric { 121 | type Data: GraphData; 122 | } 123 | 124 | impl Parametric for B 125 | where 126 | B: Reborrow, 127 | B::Target: Parametric, 128 | { 129 | type Data = ::Data; 130 | } 131 | -------------------------------------------------------------------------------- /plexus/src/graph/geometry.rs: -------------------------------------------------------------------------------- 1 | //! Geometric graph traits. 2 | 3 | // Geometric traits like `FaceNormal` and `EdgeMidpoint` are defined in such a 4 | // way to reduce the contraints necessary for writing generic user code. With 5 | // few exceptions, these traits only depend on `AsPosition` being implemented by 6 | // the `Vertex` type in their definition. If a more complex implementation is 7 | // necessary, constraints are specified there so that they do not pollute user 8 | // code. 9 | 10 | use theon::ops::{Cross, Interpolate, Project}; 11 | use theon::query::Plane; 12 | use theon::space::{EuclideanSpace, FiniteDimensional, InnerSpace, Vector, VectorSpace}; 13 | use theon::{AsPosition, Position}; 14 | use typenum::U3; 15 | 16 | use crate::entity::borrow::Reborrow; 17 | use crate::entity::storage::AsStorage; 18 | use crate::graph::data::{GraphData, Parametric}; 19 | use crate::graph::edge::{Arc, ArcView, Edge, ToArc}; 20 | use crate::graph::face::{Face, ToRing}; 21 | use crate::graph::mutation::Consistent; 22 | use crate::graph::vertex::{Vertex, VertexView}; 23 | use crate::graph::{GraphError, OptionExt as _, ResultExt as _}; 24 | use crate::IteratorExt as _; 25 | 26 | pub type VertexPosition = Position<::Vertex>; 27 | 28 | pub trait VertexCentroid: GraphData 29 | where 30 | Self::Vertex: AsPosition, 31 | { 32 | fn centroid(vertex: VertexView) -> Result, GraphError> 33 | where 34 | B: Reborrow, 35 | B::Target: 36 | AsStorage> + AsStorage> + Consistent + Parametric; 37 | } 38 | 39 | impl VertexCentroid for G 40 | where 41 | G: GraphData, 42 | G::Vertex: AsPosition, 43 | { 44 | fn centroid(vertex: VertexView) -> Result, GraphError> 45 | where 46 | B: Reborrow, 47 | B::Target: 48 | AsStorage> + AsStorage> + Consistent + Parametric, 49 | { 50 | Ok(VertexPosition::::centroid( 51 | vertex 52 | .adjacent_vertices() 53 | .map(|vertex| *vertex.data.as_position()), 54 | ) 55 | .expect_consistent()) 56 | } 57 | } 58 | 59 | pub trait VertexNormal: FaceNormal 60 | where 61 | Self::Vertex: AsPosition, 62 | { 63 | fn normal(vertex: VertexView) -> Result>, GraphError> 64 | where 65 | B: Reborrow, 66 | B::Target: AsStorage> 67 | + AsStorage> 68 | + AsStorage> 69 | + Consistent 70 | + Parametric; 71 | } 72 | 73 | impl VertexNormal for G 74 | where 75 | G: FaceNormal, 76 | G::Vertex: AsPosition, 77 | { 78 | fn normal(vertex: VertexView) -> Result>, GraphError> 79 | where 80 | B: Reborrow, 81 | B::Target: AsStorage> 82 | + AsStorage> 83 | + AsStorage> 84 | + Consistent 85 | + Parametric, 86 | { 87 | Vector::>::mean( 88 | vertex 89 | .adjacent_faces() 90 | .map(::normal) 91 | .collect::, _>>()?, 92 | ) 93 | .expect_consistent() 94 | .normalize() 95 | .ok_or(GraphError::Geometry) 96 | } 97 | } 98 | 99 | pub trait ArcNormal: GraphData 100 | where 101 | Self::Vertex: AsPosition, 102 | { 103 | fn normal(arc: ArcView) -> Result>, GraphError> 104 | where 105 | B: Reborrow, 106 | B::Target: 107 | AsStorage> + AsStorage> + Consistent + Parametric; 108 | } 109 | 110 | impl ArcNormal for G 111 | where 112 | G: GraphData, 113 | G::Vertex: AsPosition, 114 | VertexPosition: EuclideanSpace, 115 | Vector>: Project>>, 116 | { 117 | fn normal(arc: ArcView) -> Result>, GraphError> 118 | where 119 | B: Reborrow, 120 | B::Target: 121 | AsStorage> + AsStorage> + Consistent + Parametric, 122 | { 123 | let (a, b) = arc 124 | .adjacent_vertices() 125 | .map(|vertex| *vertex.position()) 126 | .try_collect() 127 | .expect_consistent(); 128 | let c = *arc.next_arc().destination_vertex().position(); 129 | let ab = a - b; 130 | let cb = c - b; 131 | let p = b + ab.project(cb); 132 | (p - c).normalize().ok_or(GraphError::Geometry) 133 | } 134 | } 135 | 136 | pub trait EdgeMidpoint: GraphData 137 | where 138 | Self::Vertex: AsPosition, 139 | { 140 | fn midpoint(edge: T) -> Result, GraphError> 141 | where 142 | B: Reborrow, 143 | B::Target: AsStorage> 144 | + AsStorage> 145 | + AsStorage> 146 | + Consistent 147 | + Parametric, 148 | T: ToArc; 149 | } 150 | 151 | impl EdgeMidpoint for G 152 | where 153 | G: GraphData, 154 | G::Vertex: AsPosition, 155 | VertexPosition: Interpolate>, 156 | { 157 | fn midpoint(edge: T) -> Result, GraphError> 158 | where 159 | B: Reborrow, 160 | B::Target: AsStorage> 161 | + AsStorage> 162 | + AsStorage> 163 | + Consistent 164 | + Parametric, 165 | T: ToArc, 166 | { 167 | let arc = edge.into_arc(); 168 | let (a, b) = arc 169 | .adjacent_vertices() 170 | .map(|vertex| *vertex.position()) 171 | .try_collect() 172 | .expect_consistent(); 173 | Ok(a.midpoint(b)) 174 | } 175 | } 176 | 177 | pub trait FaceCentroid: GraphData 178 | where 179 | Self::Vertex: AsPosition, 180 | { 181 | fn centroid(ring: T) -> Result, GraphError> 182 | where 183 | B: Reborrow, 184 | B::Target: 185 | AsStorage> + AsStorage> + Consistent + Parametric, 186 | T: ToRing; 187 | } 188 | 189 | impl FaceCentroid for G 190 | where 191 | G: GraphData, 192 | G::Vertex: AsPosition, 193 | { 194 | fn centroid(ring: T) -> Result, GraphError> 195 | where 196 | B: Reborrow, 197 | B::Target: 198 | AsStorage> + AsStorage> + Consistent + Parametric, 199 | T: ToRing, 200 | { 201 | let ring = ring.into_ring(); 202 | Ok( 203 | VertexPosition::::centroid(ring.vertices().map(|vertex| *vertex.position())) 204 | .expect_consistent(), 205 | ) 206 | } 207 | } 208 | 209 | pub trait FaceNormal: GraphData 210 | where 211 | Self::Vertex: AsPosition, 212 | { 213 | fn normal(ring: T) -> Result>, GraphError> 214 | where 215 | B: Reborrow, 216 | B::Target: 217 | AsStorage> + AsStorage> + Consistent + Parametric, 218 | T: ToRing; 219 | } 220 | 221 | impl FaceNormal for G 222 | where 223 | G: FaceCentroid + GraphData, 224 | G::Vertex: AsPosition, 225 | Vector>: Cross>>, 226 | VertexPosition: EuclideanSpace, 227 | { 228 | fn normal(ring: T) -> Result>, GraphError> 229 | where 230 | B: Reborrow, 231 | B::Target: 232 | AsStorage> + AsStorage> + Consistent + Parametric, 233 | T: ToRing, 234 | { 235 | let ring = ring.into_ring(); 236 | let (a, b) = ring 237 | .vertices() 238 | .take(2) 239 | .map(|vertex| *vertex.position()) 240 | .try_collect() 241 | .expect_consistent(); 242 | let c = G::centroid(ring)?; 243 | let ab = a - b; 244 | let bc = b - c; 245 | ab.cross(bc).normalize().ok_or(GraphError::Geometry) 246 | } 247 | } 248 | 249 | pub trait FacePlane: GraphData 250 | where 251 | Self::Vertex: AsPosition, 252 | VertexPosition: FiniteDimensional, 253 | { 254 | fn plane(ring: T) -> Result>, GraphError> 255 | where 256 | B: Reborrow, 257 | B::Target: 258 | AsStorage> + AsStorage> + Consistent + Parametric, 259 | T: ToRing; 260 | } 261 | 262 | // TODO: The `lapack` feature depends on `ndarray-linalg` and Intel MKL. MKL is 263 | // dynamically linked, but the linkage fails during doctests and may fail 264 | // when launching a binary. The `lapack` feature and this implementation 265 | // have been disabled until an upstream fix is available. See 266 | // https://github.com/olson-sean-k/plexus/issues/58 and 267 | // https://github.com/rust-ndarray/ndarray-linalg/issues/229 268 | // TODO: The `lapack` feature only supports Linux. See 269 | // https://github.com/olson-sean-k/theon/issues/1 270 | // 271 | //#[cfg(target_os = "linux")] 272 | //mod lapack { 273 | // use super::*; 274 | // 275 | // use smallvec::SmallVec; 276 | // use theon::adjunct::{FromItems, IntoItems}; 277 | // use theon::lapack::Lapack; 278 | // use theon::space::Scalar; 279 | // 280 | // impl FacePlane for G 281 | // where 282 | // G: GraphData, 283 | // G::Vertex: AsPosition, 284 | // VertexPosition: EuclideanSpace + FiniteDimensional, 285 | // Scalar>: Lapack, 286 | // Vector>: FromItems + IntoItems, 287 | // { 288 | // fn plane(ring: T) -> Result>, GraphError> 289 | // where 290 | // B: Reborrow, 291 | // B::Target: AsStorage> 292 | // + AsStorage> 293 | // + Consistent 294 | // + Parametric, 295 | // T: ToRing, 296 | // { 297 | // let ring = ring.into_ring(); 298 | // let points = ring 299 | // .vertices() 300 | // .map(|vertex| *vertex.data.as_position()) 301 | // .collect::>(); 302 | // Plane::from_points(points).ok_or(GraphError::Geometry) 303 | // } 304 | // } 305 | //} 306 | -------------------------------------------------------------------------------- /plexus/src/graph/mutation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod edge; 2 | pub mod face; 3 | pub mod path; 4 | pub mod vertex; 5 | 6 | use std::marker::PhantomData; 7 | use std::ops::{Deref, DerefMut}; 8 | 9 | use crate::entity::storage::{AsStorage, AsStorageMut, StorageTarget}; 10 | use crate::entity::Entity; 11 | use crate::graph::core::OwnedCore; 12 | use crate::graph::data::{Data, Parametric}; 13 | use crate::graph::edge::{Arc, Edge}; 14 | use crate::graph::face::Face; 15 | use crate::graph::mutation::face::FaceMutation; 16 | use crate::graph::vertex::Vertex; 17 | use crate::graph::{GraphData, GraphError}; 18 | use crate::transact::{Bypass, Transact}; 19 | 20 | // TODO: The stable toolchain does not allow a type parameter `G` to be 21 | // introduced and bound to the associated type `Mode::Graph::Data`. The 22 | // compiler does not seem to consider the types equal, and requires 23 | // redundant type bounds on `Mode`'s associated storage types at each 24 | // usage. The nightly toolchain already supports this. Reintroduce a 25 | // `G: GraphData` type parameter in implementation blocks when this is 26 | // fixed. For now, this code uses `Data`. See the following 27 | // related issues: 28 | // 29 | // https://github.com/rust-lang/rust/issues/58231 30 | // https://github.com/rust-lang/rust/issues/70703 31 | // https://github.com/rust-lang/rust/issues/47897 32 | 33 | /// Marker trait for graph representations that promise to be in a consistent 34 | /// state. 35 | /// 36 | /// This trait is only implemented by representations that ensure that their 37 | /// storage is only ever mutated via the mutation API (and therefore is 38 | /// consistent). Note that `Core` does not implement this trait and instead acts 39 | /// as a raw container for topological storage that can be freely manipulated. 40 | /// 41 | /// This trait allows code to make assumptions about the data it operates 42 | /// against. For example, views expose an API to user code that assumes that 43 | /// topologies are present and therefore unwraps values. 44 | pub trait Consistent {} 45 | 46 | impl Consistent for &'_ T where T: Consistent {} 47 | 48 | impl Consistent for &'_ mut T where T: Consistent {} 49 | 50 | pub trait Mode { 51 | type Graph: Parametric; 52 | type VertexStorage: AsStorageMut>>; 53 | type ArcStorage: AsStorageMut>>; 54 | type EdgeStorage: AsStorageMut>>; 55 | type FaceStorage: AsStorageMut>>; 56 | } 57 | 58 | pub struct Immediate 59 | where 60 | M: Parametric, 61 | { 62 | phantom: PhantomData M>, 63 | } 64 | 65 | impl Mode for Immediate 66 | where 67 | M: Parametric, 68 | { 69 | type Graph = M; 70 | type VertexStorage = > as Entity>::Storage; 71 | type ArcStorage = > as Entity>::Storage; 72 | type EdgeStorage = > as Entity>::Storage; 73 | type FaceStorage = > as Entity>::Storage; 74 | } 75 | 76 | /// Graph mutation. 77 | pub struct Mutation

78 | where 79 | P: Mode, 80 | P::Graph: Consistent + From>> + Into>>, 81 | { 82 | inner: FaceMutation

, 83 | } 84 | 85 | impl

AsRef for Mutation

86 | where 87 | P: Mode, 88 | P::Graph: Consistent + From>> + Into>>, 89 | { 90 | fn as_ref(&self) -> &Self { 91 | self 92 | } 93 | } 94 | 95 | impl

AsMut for Mutation

96 | where 97 | P: Mode, 98 | P::Graph: Consistent + From>> + Into>>, 99 | { 100 | fn as_mut(&mut self) -> &mut Self { 101 | self 102 | } 103 | } 104 | 105 | impl

AsStorage>> for Mutation

106 | where 107 | P: Mode, 108 | P::Graph: Consistent + From>> + Into>>, 109 | { 110 | fn as_storage(&self) -> &StorageTarget>> { 111 | self.inner.to_ref_core().unfuse().1 112 | } 113 | } 114 | 115 | impl

AsStorage>> for Mutation

116 | where 117 | P: Mode, 118 | P::Graph: Consistent + From>> + Into>>, 119 | { 120 | fn as_storage(&self) -> &StorageTarget>> { 121 | self.inner.to_ref_core().unfuse().2 122 | } 123 | } 124 | 125 | impl

AsStorage>> for Mutation

126 | where 127 | P: Mode, 128 | P::Graph: Consistent + From>> + Into>>, 129 | { 130 | fn as_storage(&self) -> &StorageTarget>> { 131 | self.inner.to_ref_core().unfuse().3 132 | } 133 | } 134 | 135 | impl

AsStorage>> for Mutation

136 | where 137 | P: Mode, 138 | P::Graph: Consistent + From>> + Into>>, 139 | { 140 | fn as_storage(&self) -> &StorageTarget>> { 141 | self.inner.to_ref_core().unfuse().0 142 | } 143 | } 144 | 145 | impl Bypass for Mutation> 146 | where 147 | M: Consistent + From>> + Parametric + Into>>, 148 | { 149 | fn bypass(self) -> Self::Commit { 150 | self.inner.bypass().into() 151 | } 152 | } 153 | 154 | // TODO: This is a hack. Replace this with delegation. 155 | impl

Deref for Mutation

156 | where 157 | P: Mode, 158 | P::Graph: Consistent + From>> + Into>>, 159 | { 160 | type Target = FaceMutation

; 161 | 162 | fn deref(&self) -> &Self::Target { 163 | &self.inner 164 | } 165 | } 166 | 167 | impl

DerefMut for Mutation

168 | where 169 | P: Mode, 170 | P::Graph: Consistent + From>> + Into>>, 171 | { 172 | fn deref_mut(&mut self) -> &mut Self::Target { 173 | &mut self.inner 174 | } 175 | } 176 | 177 | impl From for Mutation> 178 | where 179 | M: Consistent + From>> + Parametric + Into>>, 180 | { 181 | fn from(graph: M) -> Self { 182 | Mutation { 183 | inner: graph.into().into(), 184 | } 185 | } 186 | } 187 | 188 | impl

Parametric for Mutation

189 | where 190 | P: Mode, 191 | P::Graph: Consistent + From>> + Into>>, 192 | { 193 | type Data = Data; 194 | } 195 | 196 | impl Transact for Mutation> 197 | where 198 | M: Consistent + From>> + Parametric + Into>>, 199 | { 200 | type Commit = M; 201 | type Abort = (); 202 | type Error = GraphError; 203 | 204 | fn commit(self) -> Result { 205 | self.inner.commit().map(|core| core.into()) 206 | } 207 | 208 | fn abort(self) -> Self::Abort {} 209 | } 210 | 211 | pub trait Mutable: 212 | Consistent + From>> + Parametric + Into>> 213 | { 214 | } 215 | 216 | impl Mutable for M 217 | where 218 | M: Consistent + From> + Parametric + Into>, 219 | G: GraphData, 220 | { 221 | } 222 | -------------------------------------------------------------------------------- /plexus/src/graph/mutation/path.rs: -------------------------------------------------------------------------------- 1 | use smallvec::SmallVec; 2 | 3 | use crate::entity::borrow::Reborrow; 4 | use crate::entity::storage::AsStorage; 5 | use crate::entity::view::Bind; 6 | use crate::graph::data::{Data, GraphData, Parametric}; 7 | use crate::graph::edge::Arc; 8 | use crate::graph::face::{Face, FaceKey}; 9 | use crate::graph::mutation::face::{self, FaceInsertCache}; 10 | use crate::graph::mutation::vertex; 11 | use crate::graph::mutation::{Consistent, Mode, Mutable, Mutation}; 12 | use crate::graph::path::Path; 13 | use crate::graph::vertex::{Vertex, VertexKey, VertexView}; 14 | use crate::graph::GraphError; 15 | use crate::IteratorExt as _; 16 | 17 | pub struct PathExtrudeCache { 18 | // Avoid allocations for single arc extrusions. 19 | sources: SmallVec<[VertexKey; 2]>, 20 | } 21 | 22 | impl PathExtrudeCache { 23 | pub fn from_path(path: Path) -> Result 24 | where 25 | B: Reborrow, 26 | B::Target: AsStorage>> 27 | + AsStorage>> 28 | + AsStorage>> 29 | + Consistent 30 | + Parametric, 31 | { 32 | if path.arcs().any(|arc| !arc.is_boundary_arc()) { 33 | Err(GraphError::TopologyMalformed) 34 | } 35 | else { 36 | Ok(PathExtrudeCache { 37 | sources: path.vertices().keys().collect(), 38 | }) 39 | } 40 | } 41 | } 42 | 43 | pub fn extrude_contour_with( 44 | mut mutation: N, 45 | cache: PathExtrudeCache, 46 | f: F, 47 | ) -> Result 48 | where 49 | N: AsMut>, 50 | P: Mode, 51 | P::Graph: Mutable, 52 | F: Fn(& as GraphData>::Vertex) -> as GraphData>::Vertex, 53 | { 54 | let PathExtrudeCache { sources } = cache; 55 | let destinations: SmallVec<[_; 2]> = sources 56 | .iter() 57 | .cloned() 58 | .rev() 59 | .map(|source| -> Result<_, GraphError> { 60 | let vertex = 61 | VertexView::bind(mutation.as_mut(), source).ok_or(GraphError::TopologyNotFound)?; 62 | let data = f(vertex.get()); 63 | Ok(vertex::insert(mutation.as_mut(), data)) 64 | }) 65 | .collect::>()?; 66 | let cache = 67 | FaceInsertCache::from_storage(mutation.as_mut(), sources.into_iter().chain(destinations))?; 68 | face::insert_with(mutation.as_mut(), cache, Default::default) 69 | } 70 | -------------------------------------------------------------------------------- /plexus/src/graph/mutation/vertex.rs: -------------------------------------------------------------------------------- 1 | use crate::entity::borrow::Reborrow; 2 | use crate::entity::storage::prelude::*; 3 | use crate::entity::storage::{AsStorage, AsStorageMut, Fuse, StorageTarget}; 4 | use crate::graph::core::Core; 5 | use crate::graph::data::{Data, GraphData, Parametric}; 6 | use crate::graph::edge::ArcKey; 7 | use crate::graph::mutation::edge::{self, EdgeRemoveCache}; 8 | use crate::graph::mutation::{Consistent, Immediate, Mode, Mutable, Mutation}; 9 | use crate::graph::vertex::{Vertex, VertexKey, VertexView}; 10 | use crate::graph::GraphError; 11 | use crate::transact::{Bypass, Transact}; 12 | 13 | type ModalCore

= Core::Graph>,

::VertexStorage, (), (), ()>; 14 | type RefCore<'a, G> = Core>, (), (), ()>; 15 | 16 | pub struct VertexMutation

17 | where 18 | P: Mode, 19 | { 20 | storage: P::VertexStorage, 21 | } 22 | 23 | impl

VertexMutation

24 | where 25 | P: Mode, 26 | { 27 | pub fn to_ref_core(&self) -> RefCore> { 28 | Core::empty().fuse(self.storage.as_storage()) 29 | } 30 | 31 | pub fn connect_outgoing_arc(&mut self, a: VertexKey, ab: ArcKey) -> Result<(), GraphError> { 32 | self.with_vertex_mut(a, |vertex| vertex.arc = Some(ab)) 33 | } 34 | 35 | pub fn disconnect_outgoing_arc(&mut self, a: VertexKey) -> Result, GraphError> { 36 | self.with_vertex_mut(a, |vertex| vertex.arc.take()) 37 | } 38 | 39 | fn with_vertex_mut(&mut self, a: VertexKey, mut f: F) -> Result 40 | where 41 | F: FnMut(&mut Vertex>) -> T, 42 | { 43 | let vertex = self 44 | .storage 45 | .as_storage_mut() 46 | .get_mut(&a) 47 | .ok_or(GraphError::TopologyNotFound)?; 48 | Ok(f(vertex)) 49 | } 50 | } 51 | 52 | impl

AsStorage>> for VertexMutation

53 | where 54 | P: Mode, 55 | { 56 | fn as_storage(&self) -> &StorageTarget>> { 57 | self.storage.as_storage() 58 | } 59 | } 60 | 61 | impl Bypass>> for VertexMutation> 62 | where 63 | M: Parametric, 64 | { 65 | fn bypass(self) -> Self::Commit { 66 | let VertexMutation { 67 | storage: vertices, .. 68 | } = self; 69 | Core::empty().fuse(vertices) 70 | } 71 | } 72 | 73 | impl

From> for VertexMutation

74 | where 75 | P: Mode, 76 | { 77 | fn from(core: ModalCore

) -> Self { 78 | let (vertices, ..) = core.unfuse(); 79 | VertexMutation { storage: vertices } 80 | } 81 | } 82 | 83 | impl Transact>> for VertexMutation> 84 | where 85 | M: Parametric, 86 | { 87 | type Commit = ModalCore>; 88 | type Abort = (); 89 | type Error = GraphError; 90 | 91 | fn commit(self) -> Result { 92 | let VertexMutation { 93 | storage: vertices, .. 94 | } = self; 95 | // In a consistent graph, all vertices must have a leading arc. 96 | for (_, vertex) in vertices.as_storage().iter() { 97 | if vertex.arc.is_none() { 98 | return Err(((), GraphError::TopologyMalformed)); 99 | } 100 | } 101 | Ok(Core::empty().fuse(vertices)) 102 | } 103 | 104 | fn abort(self) -> Self::Abort {} 105 | } 106 | 107 | pub struct VertexRemoveCache { 108 | cache: Vec, 109 | } 110 | 111 | impl VertexRemoveCache { 112 | pub fn from_vertex(vertex: VertexView) -> Result 113 | where 114 | B: Reborrow, 115 | B::Target: AsStorage>> + Consistent + Parametric, 116 | { 117 | let _ = vertex; 118 | unimplemented!() 119 | } 120 | } 121 | 122 | pub fn insert(mut mutation: N, data: as GraphData>::Vertex) -> VertexKey 123 | where 124 | N: AsMut>, 125 | P: Mode, 126 | P::Graph: Mutable, 127 | { 128 | mutation 129 | .as_mut() 130 | .storage 131 | .as_storage_mut() 132 | .insert(Vertex::new(data)) 133 | } 134 | 135 | pub fn remove( 136 | mut mutation: N, 137 | cache: VertexRemoveCache, 138 | ) -> Result>, GraphError> 139 | where 140 | N: AsMut>, 141 | P: Mode, 142 | P::Graph: Mutable, 143 | { 144 | let VertexRemoveCache { cache } = cache; 145 | for cache in cache { 146 | edge::remove(mutation.as_mut(), cache)?; 147 | } 148 | unimplemented!() 149 | } 150 | -------------------------------------------------------------------------------- /plexus/src/integration/cgmath.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "geometry-cgmath")] 2 | 3 | use cgmath::{Point2, Point3, Vector2, Vector3}; 4 | use decorum::{ExtendedReal, Primitive, Real, Total}; 5 | use num::{NumCast, ToPrimitive}; 6 | 7 | use crate::geometry::{FromGeometry, UnitGeometry}; 8 | use crate::graph::GraphData; 9 | 10 | impl FromGeometry<(U, U)> for Vector2 11 | where 12 | T: NumCast, 13 | U: ToPrimitive, 14 | { 15 | fn from_geometry(other: (U, U)) -> Self { 16 | Vector2::new(T::from(other.0).unwrap(), T::from(other.1).unwrap()) 17 | } 18 | } 19 | 20 | impl FromGeometry> for (U, U) 21 | where 22 | T: ToPrimitive, 23 | U: NumCast, 24 | { 25 | fn from_geometry(other: Vector2) -> Self { 26 | (U::from(other.x).unwrap(), U::from(other.y).unwrap()) 27 | } 28 | } 29 | 30 | impl FromGeometry<(U, U, U)> for Vector3 31 | where 32 | T: NumCast, 33 | U: ToPrimitive, 34 | { 35 | fn from_geometry(other: (U, U, U)) -> Self { 36 | Vector3::new( 37 | T::from(other.0).unwrap(), 38 | T::from(other.1).unwrap(), 39 | T::from(other.2).unwrap(), 40 | ) 41 | } 42 | } 43 | 44 | impl FromGeometry> for (U, U, U) 45 | where 46 | T: ToPrimitive, 47 | U: NumCast, 48 | { 49 | fn from_geometry(other: Vector3) -> Self { 50 | ( 51 | U::from(other.x).unwrap(), 52 | U::from(other.y).unwrap(), 53 | U::from(other.z).unwrap(), 54 | ) 55 | } 56 | } 57 | 58 | impl FromGeometry<(U, U)> for Point2 59 | where 60 | T: NumCast, 61 | U: ToPrimitive, 62 | { 63 | fn from_geometry(other: (U, U)) -> Self { 64 | Point2::new(T::from(other.0).unwrap(), T::from(other.1).unwrap()) 65 | } 66 | } 67 | 68 | impl FromGeometry> for (U, U) 69 | where 70 | T: ToPrimitive, 71 | U: NumCast, 72 | { 73 | fn from_geometry(other: Point2) -> Self { 74 | (U::from(other.x).unwrap(), U::from(other.y).unwrap()) 75 | } 76 | } 77 | 78 | impl UnitGeometry for Point2 {} 79 | 80 | impl FromGeometry<(U, U, U)> for Point3 81 | where 82 | T: NumCast, 83 | U: ToPrimitive, 84 | { 85 | fn from_geometry(other: (U, U, U)) -> Self { 86 | Point3::new( 87 | T::from(other.0).unwrap(), 88 | T::from(other.1).unwrap(), 89 | T::from(other.2).unwrap(), 90 | ) 91 | } 92 | } 93 | 94 | impl FromGeometry> for (U, U, U) 95 | where 96 | T: ToPrimitive, 97 | U: NumCast, 98 | { 99 | fn from_geometry(other: Point3) -> Self { 100 | ( 101 | U::from(other.x).unwrap(), 102 | U::from(other.y).unwrap(), 103 | U::from(other.z).unwrap(), 104 | ) 105 | } 106 | } 107 | 108 | impl GraphData for Point2 109 | where 110 | Self: Copy, 111 | { 112 | type Vertex = Self; 113 | type Arc = (); 114 | type Edge = (); 115 | type Face = (); 116 | } 117 | 118 | impl GraphData for Point3 119 | where 120 | Self: Copy, 121 | { 122 | type Vertex = Self; 123 | type Arc = (); 124 | type Edge = (); 125 | type Face = (); 126 | } 127 | 128 | impl UnitGeometry for Point3 {} 129 | 130 | macro_rules! with_constrained_scalars { 131 | ($f:ident) => { 132 | $f!(proxy => Real); 133 | $f!(proxy => ExtendedReal); 134 | $f!(proxy => Total); 135 | }; 136 | } 137 | 138 | macro_rules! with_geometric_structures { 139 | ($f:ident) => { 140 | $f!(geometry => Vector2); 141 | $f!(geometry => Vector3); 142 | $f!(geometry => Point2); 143 | $f!(geometry => Point3); 144 | }; 145 | } 146 | 147 | macro_rules! impl_from_geometry_for_constrained_scalar_structures { 148 | () => { 149 | with_constrained_scalars!(impl_from_geometry_for_constrained_scalar_structures); 150 | }; 151 | (proxy => $p:ident) => { 152 | macro_rules! impl_from_geometry_for_scalar_structure { 153 | () => { 154 | with_geometric_structures!(impl_from_geometry_for_scalar_structure); 155 | }; 156 | (geometry => $g:ident) => { 157 | impl FromGeometry<$g<$p>> for $g 158 | where 159 | T: Primitive, 160 | { 161 | fn from_geometry(other: $g<$p>) -> Self { 162 | other.map(|value| value.into_inner()) 163 | } 164 | } 165 | 166 | impl FromGeometry<$g> for $g<$p> 167 | where 168 | T: Primitive, 169 | { 170 | fn from_geometry(other: $g) -> Self { 171 | other.map($p::::assert) 172 | } 173 | } 174 | }; 175 | } 176 | impl_from_geometry_for_scalar_structure!(); 177 | }; 178 | } 179 | impl_from_geometry_for_constrained_scalar_structures!(); 180 | -------------------------------------------------------------------------------- /plexus/src/integration/glam.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "geometry-glam")] 2 | 3 | use glam::{Vec2, Vec3, Vec3A}; 4 | 5 | use crate::geometry::{FromGeometry, UnitGeometry}; 6 | use crate::graph::GraphData; 7 | 8 | impl FromGeometry<(f32, f32)> for Vec2 { 9 | fn from_geometry(other: (f32, f32)) -> Self { 10 | Self::from(other) 11 | } 12 | } 13 | 14 | impl FromGeometry<(f32, f32, f32)> for Vec3 { 15 | fn from_geometry(other: (f32, f32, f32)) -> Self { 16 | Self::from(other) 17 | } 18 | } 19 | 20 | impl FromGeometry<(f32, f32, f32)> for Vec3A { 21 | fn from_geometry(other: (f32, f32, f32)) -> Self { 22 | Self::from(other) 23 | } 24 | } 25 | 26 | impl GraphData for Vec2 { 27 | type Vertex = Self; 28 | type Arc = (); 29 | type Edge = (); 30 | type Face = (); 31 | } 32 | 33 | impl GraphData for Vec3 { 34 | type Vertex = Self; 35 | type Arc = (); 36 | type Edge = (); 37 | type Face = (); 38 | } 39 | 40 | impl GraphData for Vec3A { 41 | type Vertex = Self; 42 | type Arc = (); 43 | type Edge = (); 44 | type Face = (); 45 | } 46 | 47 | impl UnitGeometry for Vec2 {} 48 | 49 | impl UnitGeometry for Vec3 {} 50 | 51 | impl UnitGeometry for Vec3A {} 52 | -------------------------------------------------------------------------------- /plexus/src/integration/mint.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "geometry-mint")] 2 | 3 | use decorum::{ExtendedReal, Primitive, Real, Total}; 4 | use mint::{Point2, Point3, Vector2, Vector3}; 5 | use num::{NumCast, ToPrimitive}; 6 | 7 | use crate::geometry::{FromGeometry, UnitGeometry}; 8 | use crate::graph::GraphData; 9 | 10 | impl FromGeometry<(U, U)> for Vector2 11 | where 12 | T: NumCast, 13 | U: ToPrimitive, 14 | { 15 | fn from_geometry(other: (U, U)) -> Self { 16 | Vector2 { 17 | x: T::from(other.0).unwrap(), 18 | y: T::from(other.1).unwrap(), 19 | } 20 | } 21 | } 22 | 23 | impl FromGeometry> for (U, U) 24 | where 25 | T: ToPrimitive, 26 | U: NumCast, 27 | { 28 | fn from_geometry(other: Vector2) -> Self { 29 | (U::from(other.x).unwrap(), U::from(other.y).unwrap()) 30 | } 31 | } 32 | 33 | impl FromGeometry<(U, U, U)> for Vector3 34 | where 35 | T: NumCast, 36 | U: ToPrimitive, 37 | { 38 | fn from_geometry(other: (U, U, U)) -> Self { 39 | Vector3 { 40 | x: T::from(other.0).unwrap(), 41 | y: T::from(other.1).unwrap(), 42 | z: T::from(other.2).unwrap(), 43 | } 44 | } 45 | } 46 | 47 | impl FromGeometry> for (U, U, U) 48 | where 49 | T: ToPrimitive, 50 | U: NumCast, 51 | { 52 | fn from_geometry(other: Vector3) -> Self { 53 | ( 54 | U::from(other.x).unwrap(), 55 | U::from(other.y).unwrap(), 56 | U::from(other.z).unwrap(), 57 | ) 58 | } 59 | } 60 | 61 | impl FromGeometry<(U, U)> for Point2 62 | where 63 | T: NumCast, 64 | U: ToPrimitive, 65 | { 66 | fn from_geometry(other: (U, U)) -> Self { 67 | Point2 { 68 | x: T::from(other.0).unwrap(), 69 | y: T::from(other.1).unwrap(), 70 | } 71 | } 72 | } 73 | 74 | impl FromGeometry> for (U, U) 75 | where 76 | T: ToPrimitive, 77 | U: NumCast, 78 | { 79 | fn from_geometry(other: Point2) -> Self { 80 | (U::from(other.x).unwrap(), U::from(other.y).unwrap()) 81 | } 82 | } 83 | 84 | impl UnitGeometry for Point2 {} 85 | 86 | impl FromGeometry<(U, U, U)> for Point3 87 | where 88 | T: NumCast, 89 | U: ToPrimitive, 90 | { 91 | fn from_geometry(other: (U, U, U)) -> Self { 92 | Point3 { 93 | x: T::from(other.0).unwrap(), 94 | y: T::from(other.1).unwrap(), 95 | z: T::from(other.2).unwrap(), 96 | } 97 | } 98 | } 99 | 100 | impl FromGeometry> for (U, U, U) 101 | where 102 | T: ToPrimitive, 103 | U: NumCast, 104 | { 105 | fn from_geometry(other: Point3) -> Self { 106 | ( 107 | U::from(other.x).unwrap(), 108 | U::from(other.y).unwrap(), 109 | U::from(other.z).unwrap(), 110 | ) 111 | } 112 | } 113 | 114 | impl GraphData for Point2 115 | where 116 | Self: Copy, 117 | { 118 | type Vertex = Self; 119 | type Arc = (); 120 | type Edge = (); 121 | type Face = (); 122 | } 123 | 124 | impl GraphData for Point3 125 | where 126 | Self: Copy, 127 | { 128 | type Vertex = Self; 129 | type Arc = (); 130 | type Edge = (); 131 | type Face = (); 132 | } 133 | 134 | impl UnitGeometry for Point3 {} 135 | 136 | macro_rules! with_constrained_scalars { 137 | ($f:ident) => { 138 | $f!(proxy => Real); 139 | $f!(proxy => ExtendedReal); 140 | $f!(proxy => Total); 141 | }; 142 | } 143 | 144 | macro_rules! impl_from_geometry_for_constrained_scalar_structures { 145 | () => { 146 | with_constrained_scalars!(impl_from_geometry_for_constrained_scalar_structures); 147 | }; 148 | (proxy => $p:ident) => { 149 | impl FromGeometry>> for Vector2 150 | where 151 | T: Primitive, 152 | { 153 | fn from_geometry(other: Vector2<$p>) -> Self { 154 | Vector2 { 155 | x: other.x.into_inner(), 156 | y: other.y.into_inner(), 157 | } 158 | } 159 | } 160 | 161 | impl FromGeometry> for Vector2<$p> 162 | where 163 | T: Primitive, 164 | { 165 | fn from_geometry(other: Vector2) -> Self { 166 | Vector2 { 167 | x: $p::::assert(other.x), 168 | y: $p::::assert(other.y), 169 | } 170 | } 171 | } 172 | 173 | impl FromGeometry>> for Vector3 174 | where 175 | T: Primitive, 176 | { 177 | fn from_geometry(other: Vector3<$p>) -> Self { 178 | Vector3 { 179 | x: other.x.into_inner(), 180 | y: other.y.into_inner(), 181 | z: other.z.into_inner(), 182 | } 183 | } 184 | } 185 | 186 | impl FromGeometry> for Vector3<$p> 187 | where 188 | T: Primitive, 189 | { 190 | fn from_geometry(other: Vector3) -> Self { 191 | Vector3 { 192 | x: $p::::assert(other.x), 193 | y: $p::::assert(other.y), 194 | z: $p::::assert(other.z), 195 | } 196 | } 197 | } 198 | 199 | impl FromGeometry>> for Point2 200 | where 201 | T: Primitive, 202 | { 203 | fn from_geometry(other: Point2<$p>) -> Self { 204 | Point2 { 205 | x: other.x.into_inner(), 206 | y: other.y.into_inner(), 207 | } 208 | } 209 | } 210 | 211 | impl FromGeometry> for Point2<$p> 212 | where 213 | T: Primitive, 214 | { 215 | fn from_geometry(other: Point2) -> Self { 216 | Point2 { 217 | x: $p::::assert(other.x), 218 | y: $p::::assert(other.y), 219 | } 220 | } 221 | } 222 | 223 | impl FromGeometry>> for Point3 224 | where 225 | T: Primitive, 226 | { 227 | fn from_geometry(other: Point3<$p>) -> Self { 228 | Point3 { 229 | x: other.x.into_inner(), 230 | y: other.y.into_inner(), 231 | z: other.z.into_inner(), 232 | } 233 | } 234 | } 235 | 236 | impl FromGeometry> for Point3<$p> 237 | where 238 | T: Primitive, 239 | { 240 | fn from_geometry(other: Point3) -> Self { 241 | Point3 { 242 | x: $p::::assert(other.x), 243 | y: $p::::assert(other.y), 244 | z: $p::::assert(other.z), 245 | } 246 | } 247 | } 248 | }; 249 | } 250 | impl_from_geometry_for_constrained_scalar_structures!(); 251 | -------------------------------------------------------------------------------- /plexus/src/integration/mod.rs: -------------------------------------------------------------------------------- 1 | //! Integration of external crates and foreign types. 2 | //! 3 | //! This module provides implementations of traits in Plexus for foreign types. 4 | 5 | // TODO: Do not implement geometric traits and conversions over tuples of scalars. Prefer array 6 | // types instead. 7 | 8 | mod cgmath; 9 | mod glam; 10 | mod mint; 11 | mod nalgebra; 12 | mod ultraviolet; 13 | -------------------------------------------------------------------------------- /plexus/src/integration/nalgebra.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "geometry-nalgebra")] 2 | 3 | use decorum::{ExtendedReal, Primitive, Real, Total}; 4 | use nalgebra::base::allocator::Allocator; 5 | use nalgebra::{ 6 | DefaultAllocator, DimName, OMatrix, OPoint, Point2, Point3, Scalar, Vector2, Vector3, 7 | }; 8 | use num::{NumCast, ToPrimitive}; 9 | 10 | use crate::geometry::{FromGeometry, UnitGeometry}; 11 | use crate::graph::GraphData; 12 | 13 | impl FromGeometry<(U, U)> for Vector2 14 | where 15 | T: NumCast + Scalar, 16 | U: ToPrimitive, 17 | { 18 | fn from_geometry(other: (U, U)) -> Self { 19 | Vector2::new(T::from(other.0).unwrap(), T::from(other.1).unwrap()) 20 | } 21 | } 22 | 23 | impl FromGeometry> for (U, U) 24 | where 25 | T: Scalar + ToPrimitive, 26 | U: NumCast, 27 | { 28 | fn from_geometry(other: Vector2) -> Self { 29 | let [x, y]: [T; 2] = other.into(); 30 | (U::from(x).unwrap(), U::from(y).unwrap()) 31 | } 32 | } 33 | 34 | impl FromGeometry<(U, U, U)> for Vector3 35 | where 36 | T: NumCast + Scalar, 37 | U: ToPrimitive, 38 | { 39 | fn from_geometry(other: (U, U, U)) -> Self { 40 | Vector3::new( 41 | T::from(other.0).unwrap(), 42 | T::from(other.1).unwrap(), 43 | T::from(other.2).unwrap(), 44 | ) 45 | } 46 | } 47 | 48 | impl FromGeometry> for (U, U, U) 49 | where 50 | T: Scalar + ToPrimitive, 51 | U: NumCast, 52 | { 53 | fn from_geometry(other: Vector3) -> Self { 54 | let [x, y, z]: [T; 3] = other.into(); 55 | ( 56 | U::from(x).unwrap(), 57 | U::from(y).unwrap(), 58 | U::from(z).unwrap(), 59 | ) 60 | } 61 | } 62 | 63 | impl FromGeometry<(U, U)> for Point2 64 | where 65 | T: NumCast + Scalar, 66 | U: ToPrimitive, 67 | { 68 | fn from_geometry(other: (U, U)) -> Self { 69 | Point2::new(T::from(other.0).unwrap(), T::from(other.1).unwrap()) 70 | } 71 | } 72 | 73 | impl FromGeometry> for (U, U) 74 | where 75 | T: Scalar + ToPrimitive, 76 | U: NumCast, 77 | { 78 | fn from_geometry(other: Point2) -> Self { 79 | let [x, y]: [T; 2] = other.coords.into(); 80 | (U::from(x).unwrap(), U::from(y).unwrap()) 81 | } 82 | } 83 | 84 | impl FromGeometry<(U, U, U)> for Point3 85 | where 86 | T: NumCast + Scalar, 87 | U: ToPrimitive, 88 | { 89 | fn from_geometry(other: (U, U, U)) -> Self { 90 | Point3::new( 91 | T::from(other.0).unwrap(), 92 | T::from(other.1).unwrap(), 93 | T::from(other.2).unwrap(), 94 | ) 95 | } 96 | } 97 | 98 | impl FromGeometry> for (U, U, U) 99 | where 100 | T: Scalar + ToPrimitive, 101 | U: NumCast, 102 | { 103 | fn from_geometry(other: Point3) -> Self { 104 | let [x, y, z]: [T; 3] = other.coords.into(); 105 | ( 106 | U::from(x).unwrap(), 107 | U::from(y).unwrap(), 108 | U::from(z).unwrap(), 109 | ) 110 | } 111 | } 112 | 113 | impl GraphData for OPoint 114 | where 115 | T: Scalar, 116 | D: DimName, 117 | DefaultAllocator: Allocator, 118 | Self: Copy, 119 | { 120 | type Vertex = Self; 121 | type Arc = (); 122 | type Edge = (); 123 | type Face = (); 124 | } 125 | 126 | impl UnitGeometry for OPoint 127 | where 128 | T: Scalar, 129 | D: DimName, 130 | DefaultAllocator: Allocator, 131 | { 132 | } 133 | 134 | macro_rules! with_constrained_scalars { 135 | ($f:ident) => { 136 | $f!(proxy => Real); 137 | $f!(proxy => ExtendedReal); 138 | $f!(proxy => Total); 139 | }; 140 | } 141 | 142 | macro_rules! impl_from_geometry_for_constrained_scalar_structures { 143 | () => { 144 | with_constrained_scalars!(impl_from_geometry_for_constrained_scalar_structures); 145 | }; 146 | (proxy => $p:ident) => { 147 | impl FromGeometry, R, C>> for OMatrix 148 | where 149 | T: Primitive + Scalar, 150 | R: DimName, 151 | C: DimName, 152 | DefaultAllocator: Allocator, 153 | { 154 | fn from_geometry(other: OMatrix<$p, R, C>) -> Self { 155 | other.map(|value| value.into_inner()) 156 | } 157 | } 158 | 159 | impl FromGeometry> for OMatrix<$p, R, C> 160 | where 161 | T: Primitive + Scalar, 162 | R: DimName, 163 | C: DimName, 164 | DefaultAllocator: Allocator, 165 | { 166 | fn from_geometry(other: OMatrix) -> Self { 167 | other.map($p::::assert) 168 | } 169 | } 170 | 171 | impl FromGeometry, D>> for OPoint 172 | where 173 | T: Primitive + Scalar, 174 | D: DimName, 175 | DefaultAllocator: Allocator, 176 | { 177 | fn from_geometry(other: OPoint<$p, D>) -> Self { 178 | OPoint::from(other.coords.map(|value| value.into_inner())) 179 | } 180 | } 181 | 182 | impl FromGeometry> for OPoint<$p, D> 183 | where 184 | T: Primitive + Scalar, 185 | D: DimName, 186 | DefaultAllocator: Allocator, 187 | { 188 | fn from_geometry(other: OPoint) -> Self { 189 | OPoint::from(other.coords.map($p::::assert)) 190 | } 191 | } 192 | }; 193 | } 194 | impl_from_geometry_for_constrained_scalar_structures!(); 195 | -------------------------------------------------------------------------------- /plexus/src/integration/ultraviolet.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "geometry-ultraviolet")] 2 | 3 | use ultraviolet::vec::{Vec2, Vec3}; 4 | 5 | use crate::geometry::{FromGeometry, UnitGeometry}; 6 | use crate::graph::GraphData; 7 | 8 | impl FromGeometry<(f32, f32)> for Vec2 { 9 | fn from_geometry(other: (f32, f32)) -> Self { 10 | Self::from(other) 11 | } 12 | } 13 | 14 | impl FromGeometry<(f32, f32, f32)> for Vec3 { 15 | fn from_geometry(other: (f32, f32, f32)) -> Self { 16 | Self::from(other) 17 | } 18 | } 19 | 20 | impl GraphData for Vec2 { 21 | type Vertex = Self; 22 | type Arc = (); 23 | type Edge = (); 24 | type Face = (); 25 | } 26 | 27 | impl GraphData for Vec3 { 28 | type Vertex = Self; 29 | type Arc = (); 30 | type Edge = (); 31 | type Face = (); 32 | } 33 | 34 | impl UnitGeometry for Vec2 {} 35 | 36 | impl UnitGeometry for Vec3 {} 37 | -------------------------------------------------------------------------------- /plexus/src/primitive/cube.rs: -------------------------------------------------------------------------------- 1 | //! Cube primitives. 2 | //! 3 | //! # Examples 4 | //! 5 | //! ```rust 6 | //! # extern crate decorum; 7 | //! # extern crate nalgebra; 8 | //! # extern crate plexus; 9 | //! # 10 | //! use decorum::R32; 11 | //! use nalgebra::Point3; 12 | //! use plexus::graph::MeshGraph; 13 | //! use plexus::prelude::*; 14 | //! use plexus::primitive::cube::Cube; 15 | //! use plexus::primitive::generate::Position; 16 | //! 17 | //! let mut graph = Cube::new() 18 | //! .polygons::>>() 19 | //! .collect::>>(); 20 | //! ``` 21 | 22 | use num::One; 23 | use theon::adjunct::{Converged, Map}; 24 | use theon::query::Unit; 25 | use theon::space::{Basis, EuclideanSpace, FiniteDimensional, InnerSpace, Scalar, Vector}; 26 | use typenum::U3; 27 | 28 | use crate::primitive::generate::{ 29 | Attribute, AttributeGenerator, AttributePolygonGenerator, AttributeVertexGenerator, Generator, 30 | IndexingPolygonGenerator, Normal, PolygonGenerator, Position, 31 | }; 32 | use crate::primitive::Tetragon; 33 | 34 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 35 | pub enum Plane { 36 | Xy, // front 37 | Nxy, // back 38 | Yz, // right 39 | Nyz, // left 40 | Xz, // bottom 41 | Nxz, // top 42 | } 43 | 44 | impl Plane { 45 | pub fn normal(self) -> Unit 46 | where 47 | S: Basis + FiniteDimensional + InnerSpace, 48 | { 49 | match self { 50 | Plane::Xy => Unit::::z(), // front 51 | Plane::Nxy => -Unit::::z(), // back 52 | Plane::Yz => Unit::::x(), // right 53 | Plane::Nyz => -Unit::::x(), // left 54 | Plane::Xz => -Unit::::y(), // bottom 55 | Plane::Nxz => Unit::::y(), // top 56 | } 57 | } 58 | } 59 | 60 | impl Attribute for Plane {} 61 | 62 | #[derive(Clone, Copy)] 63 | pub struct Bounds 64 | where 65 | S: EuclideanSpace, 66 | { 67 | lower: Scalar, 68 | upper: Scalar, 69 | } 70 | 71 | impl Bounds 72 | where 73 | S: EuclideanSpace, 74 | { 75 | pub fn with_radius(radius: Scalar) -> Self { 76 | Bounds { 77 | lower: -radius, 78 | upper: radius, 79 | } 80 | } 81 | 82 | pub fn with_width(width: Scalar) -> Self { 83 | Self::with_radius(width / (Scalar::::one() + One::one())) 84 | } 85 | 86 | pub fn unit_radius() -> Self { 87 | Self::with_radius(One::one()) 88 | } 89 | 90 | pub fn unit_width() -> Self { 91 | Self::with_width(One::one()) 92 | } 93 | } 94 | 95 | impl Default for Bounds 96 | where 97 | S: EuclideanSpace, 98 | { 99 | fn default() -> Self { 100 | Self::unit_width() 101 | } 102 | } 103 | 104 | #[derive(Clone, Copy)] 105 | pub struct Cube; 106 | 107 | impl Cube { 108 | pub fn new() -> Self { 109 | Cube 110 | } 111 | } 112 | 113 | impl Default for Cube { 114 | fn default() -> Self { 115 | Cube::new() 116 | } 117 | } 118 | 119 | impl PolygonGenerator for Cube { 120 | fn polygon_count(&self) -> usize { 121 | 6 122 | } 123 | } 124 | 125 | impl AttributeGenerator> for Cube 126 | where 127 | S: EuclideanSpace + FiniteDimensional, 128 | { 129 | type State = (); 130 | } 131 | 132 | impl AttributeVertexGenerator> for Cube 133 | where 134 | S: EuclideanSpace + FiniteDimensional, 135 | { 136 | type Output = Unit>; 137 | 138 | fn vertex_count(&self) -> usize { 139 | self.polygon_count() 140 | } 141 | 142 | fn vertex_from(&self, _: &Self::State, index: usize) -> Self::Output { 143 | AttributeVertexGenerator::::vertex_from(self, &(), index).normal::>() 144 | } 145 | } 146 | 147 | impl AttributePolygonGenerator> for Cube 148 | where 149 | S: EuclideanSpace + FiniteDimensional, 150 | { 151 | type Output = Tetragon>>; 152 | 153 | fn polygon_from(&self, state: &Self::State, index: usize) -> Self::Output { 154 | IndexingPolygonGenerator::>::indexing_polygon(self, index) 155 | .map(|index| AttributeVertexGenerator::>::vertex_from(self, state, index)) 156 | } 157 | } 158 | 159 | impl IndexingPolygonGenerator> for Cube { 160 | type Output = Tetragon; 161 | 162 | fn indexing_polygon(&self, index: usize) -> Self::Output { 163 | assert!(index < self.polygon_count()); 164 | Tetragon::converged(index) 165 | } 166 | } 167 | 168 | impl AttributeGenerator> for Cube 169 | where 170 | S: EuclideanSpace + FiniteDimensional, 171 | { 172 | type State = Bounds; 173 | } 174 | 175 | impl AttributeVertexGenerator> for Cube 176 | where 177 | S: EuclideanSpace + FiniteDimensional, 178 | { 179 | type Output = S; 180 | 181 | fn vertex_count(&self) -> usize { 182 | 8 183 | } 184 | 185 | fn vertex_from(&self, state: &Self::State, index: usize) -> Self::Output { 186 | let x = if index & 0b100 == 0b100 { 187 | state.upper 188 | } 189 | else { 190 | state.lower 191 | }; 192 | let y = if index & 0b010 == 0b010 { 193 | state.upper 194 | } 195 | else { 196 | state.lower 197 | }; 198 | let z = if index & 0b001 == 0b001 { 199 | state.upper 200 | } 201 | else { 202 | state.lower 203 | }; 204 | S::from_xyz(x, y, z) 205 | } 206 | } 207 | 208 | impl AttributePolygonGenerator> for Cube 209 | where 210 | S: EuclideanSpace + FiniteDimensional, 211 | { 212 | type Output = Tetragon; 213 | 214 | fn polygon_from(&self, state: &Self::State, index: usize) -> Self::Output { 215 | IndexingPolygonGenerator::>::indexing_polygon(self, index) 216 | .map(|index| AttributeVertexGenerator::>::vertex_from(self, state, index)) 217 | } 218 | } 219 | 220 | impl IndexingPolygonGenerator> for Cube { 221 | type Output = Tetragon; 222 | 223 | fn indexing_polygon(&self, index: usize) -> Self::Output { 224 | match index { 225 | 0 => Tetragon::new(5, 7, 3, 1), // front 226 | 1 => Tetragon::new(6, 7, 5, 4), // right 227 | 2 => Tetragon::new(3, 7, 6, 2), // top 228 | 3 => Tetragon::new(0, 1, 3, 2), // left 229 | 4 => Tetragon::new(4, 5, 1, 0), // bottom 230 | 5 => Tetragon::new(0, 2, 6, 4), // back 231 | _ => panic!(), 232 | } 233 | } 234 | } 235 | 236 | impl AttributeGenerator for Cube { 237 | type State = (); 238 | } 239 | 240 | impl AttributeVertexGenerator for Cube { 241 | type Output = Plane; 242 | 243 | fn vertex_count(&self) -> usize { 244 | self.polygon_count() 245 | } 246 | 247 | fn vertex_from(&self, _: &Self::State, index: usize) -> Self::Output { 248 | match index { 249 | 0 => Plane::Xy, // front 250 | 1 => Plane::Yz, // right 251 | 2 => Plane::Nxz, // top 252 | 3 => Plane::Nyz, // left 253 | 4 => Plane::Xz, // bottom 254 | 5 => Plane::Nxy, // back 255 | _ => panic!(), 256 | } 257 | } 258 | } 259 | 260 | impl AttributePolygonGenerator for Cube { 261 | type Output = Tetragon; 262 | 263 | fn polygon_from(&self, state: &Self::State, index: usize) -> Self::Output { 264 | IndexingPolygonGenerator::::indexing_polygon(self, index) 265 | .map(|index| AttributeVertexGenerator::::vertex_from(self, state, index)) 266 | } 267 | } 268 | 269 | impl IndexingPolygonGenerator for Cube { 270 | type Output = Tetragon; 271 | 272 | fn indexing_polygon(&self, index: usize) -> Self::Output { 273 | match index { 274 | 0 => Tetragon::converged(0), // front 275 | 1 => Tetragon::converged(1), // right 276 | 2 => Tetragon::converged(2), // top 277 | 3 => Tetragon::converged(3), // left 278 | 4 => Tetragon::converged(4), // bottom 279 | 5 => Tetragon::converged(5), // back 280 | _ => panic!(), 281 | } 282 | } 283 | } 284 | 285 | impl Generator for Cube {} 286 | -------------------------------------------------------------------------------- /plexus/src/primitive/decompose.rs: -------------------------------------------------------------------------------- 1 | //! Decomposition and tessellation. 2 | //! 3 | //! The [`Decompose`] iterator uses various traits to decompose and tessellate 4 | //! iterators of topological structures. 5 | //! 6 | //! This module provides two kinds of decomposition traits: conversions and 7 | //! [`Iterator`] extensions. Conversion traits are implemented by topological 8 | //! types and decompose a single structure into any number of output structures. 9 | //! Extensions are implemented for iterators of topological structures and 10 | //! perform the corresponding conversion on the input items to produce flattened 11 | //! output. For example, [`IntoTrigons`] converts a [`Polygonal`] type into an 12 | //! iterator of [`Trigon`]s while [`Triangulate`] does the same to the items of 13 | //! an iterator. 14 | //! 15 | //! Many of these traits are re-exported in the [`prelude`] module. 16 | //! 17 | //! # Examples 18 | //! 19 | //! Tessellating a [`Tetragon`] into [`Trigon`]s: 20 | //! 21 | //! ```rust 22 | //! # extern crate nalgebra; 23 | //! # extern crate plexus; 24 | //! # 25 | //! use nalgebra::Point2; 26 | //! use plexus::prelude::*; 27 | //! use plexus::primitive::Tetragon; 28 | //! 29 | //! type E2 = Point2; 30 | //! 31 | //! let square = Tetragon::from([ 32 | //! E2::new(1.0, 1.0), 33 | //! E2::new(-1.0, 1.0), 34 | //! E2::new(-1.0, -1.0), 35 | //! E2::new(1.0, -1.0), 36 | //! ]); 37 | //! let trigons = square.into_trigons(); 38 | //! ``` 39 | //! 40 | //! Tessellating an iterator of [`Tetragon`]s from a [generator][`generate`]: 41 | //! 42 | //! ```rust 43 | //! # extern crate nalgebra; 44 | //! # extern crate plexus; 45 | //! # 46 | //! use nalgebra::Point3; 47 | //! use plexus::prelude::*; 48 | //! use plexus::primitive::cube::Cube; 49 | //! use plexus::primitive::generate::Position; 50 | //! 51 | //! type E3 = Point3; 52 | //! 53 | //! let polygons: Vec<_> = Cube::new() 54 | //! .polygons::>() 55 | //! .subdivide() 56 | //! .triangulate() 57 | //! .collect(); 58 | //! ``` 59 | //! 60 | //! [`Iterator`]: std::iter::Iterator 61 | //! [`prelude`]: crate::prelude 62 | //! [`Decompose`]: crate::primitive::decompose::Decompose 63 | //! [`IntoTrigons`]: crate::primitive::decompose::IntoTrigons 64 | //! [`Triangulate`]: crate::primitive::decompose::Triangulate 65 | //! [`generate`]: crate::primitive::generate 66 | //! [`Polygonal`]: crate::primitive::Polygonal 67 | //! [`Tetragon`]: crate::primitive::Tetragon 68 | //! [`Trigon`]: crate::primitive::Trigon 69 | 70 | use arrayvec::ArrayVec; 71 | use std::collections::VecDeque; 72 | use std::iter::IntoIterator; 73 | use theon::ops::Interpolate; 74 | use typenum::{Cmp, Greater, U1}; 75 | 76 | use crate::constant::{Constant, ToType, TypeOf}; 77 | use crate::primitive::{ 78 | BoundedPolygon, Edge, NGon, Polygonal, Tetragon, Topological, Trigon, UnboundedPolygon, 79 | }; 80 | use crate::IteratorExt as _; 81 | 82 | pub struct Decompose 83 | where 84 | R: IntoIterator, 85 | { 86 | input: I, 87 | output: VecDeque, 88 | f: fn(P) -> R, 89 | } 90 | 91 | impl Decompose 92 | where 93 | R: IntoIterator, 94 | { 95 | pub(in crate::primitive) fn new(input: I, f: fn(P) -> R) -> Self { 96 | Decompose { 97 | input, 98 | output: VecDeque::new(), 99 | f, 100 | } 101 | } 102 | } 103 | 104 | impl Decompose 105 | where 106 | I: Iterator, 107 | R: IntoIterator, 108 | { 109 | /// Reapplies a congruent decomposition. 110 | /// 111 | /// A decomposition is _congruent_ if its input and output types are the 112 | /// same. This is useful when the number of applications is somewhat large 113 | /// or variable, in which case chaining calls is impractical or impossible. 114 | /// 115 | /// # Examples 116 | /// 117 | /// ```rust 118 | /// # extern crate decorum; 119 | /// # extern crate nalgebra; 120 | /// # extern crate plexus; 121 | /// # 122 | /// use decorum::R64; 123 | /// use nalgebra::Point3; 124 | /// use plexus::index::{Flat4, HashIndexer}; 125 | /// use plexus::prelude::*; 126 | /// use plexus::primitive::cube::Cube; 127 | /// use plexus::primitive::generate::Position; 128 | /// 129 | /// let (indices, positions) = Cube::new() 130 | /// .polygons::>>() 131 | /// .subdivide() 132 | /// .remap(7) // 8 subdivision operations are applied. 133 | /// .index_vertices::(HashIndexer::default()); 134 | /// ``` 135 | pub fn remap(self, n: usize) -> Decompose, P, P, R> { 136 | let Decompose { input, output, f } = self; 137 | Decompose::new(output.into_iter().rev().chain(remap(n, input, f)), f) 138 | } 139 | } 140 | 141 | impl Iterator for Decompose 142 | where 143 | I: Iterator, 144 | R: IntoIterator, 145 | { 146 | type Item = Q; 147 | 148 | fn next(&mut self) -> Option { 149 | loop { 150 | if let Some(ngon) = self.output.pop_front() { 151 | return Some(ngon); 152 | } 153 | if let Some(ngon) = self.input.next() { 154 | self.output.extend((self.f)(ngon)); 155 | } 156 | else { 157 | return None; 158 | } 159 | } 160 | } 161 | 162 | fn size_hint(&self) -> (usize, Option) { 163 | let (lower, _) = self.input.size_hint(); 164 | (lower, None) 165 | } 166 | } 167 | 168 | pub trait IntoVertices: Topological { 169 | type Output: IntoIterator; 170 | 171 | fn into_vertices(self) -> Self::Output; 172 | } 173 | 174 | impl IntoVertices for T 175 | where 176 | T: Topological, 177 | { 178 | type Output = ::IntoIter; 179 | 180 | fn into_vertices(self) -> Self::Output { 181 | self.into_iter() 182 | } 183 | } 184 | 185 | pub trait IntoEdges: Topological { 186 | type Output: IntoIterator>; 187 | 188 | fn into_edges(self) -> Self::Output; 189 | } 190 | 191 | pub trait IntoTrigons: Polygonal { 192 | type Output: IntoIterator>; 193 | 194 | fn into_trigons(self) -> Self::Output; 195 | } 196 | 197 | pub trait IntoSubdivisions: Polygonal { 198 | type Output: IntoIterator; 199 | 200 | fn into_subdivisions(self) -> Self::Output; 201 | } 202 | 203 | pub trait IntoTetrahedrons: Polygonal { 204 | fn into_tetrahedrons(self) -> ArrayVec, 4>; 205 | } 206 | 207 | impl IntoEdges for NGon 208 | where 209 | G: Clone, 210 | Constant: ToType, 211 | TypeOf: Cmp, 212 | { 213 | // TODO: As of Rust 1.51.1, it is not possible to constrain constant 214 | // generics nor use them in expressions. If and when this is possible, 215 | // do not implement this trait for degenerate `NGon`s and use use an 216 | // `ArrayVec, {if N == 2 { 1 } else { N }}>` as 217 | // output. 218 | type Output = Vec>; 219 | 220 | fn into_edges(self) -> Self::Output { 221 | if N == 2 { 222 | // At the time of writing, it is not possible to specialize this 223 | // case for `NGon` nor prove to the compiler that `Self` is of 224 | // type `Edge` when the condition `N == 2` is `true`. 225 | vec![self.into_iter().try_collect().unwrap()] 226 | } 227 | else { 228 | self.into_iter() 229 | .perimeter() 230 | .map(|(a, b)| Edge::new(a, b)) 231 | .collect() 232 | } 233 | } 234 | } 235 | 236 | impl IntoEdges for BoundedPolygon 237 | where 238 | T: Clone, 239 | { 240 | type Output = Vec>; 241 | 242 | fn into_edges(self) -> Self::Output { 243 | match self { 244 | BoundedPolygon::N3(trigon) => trigon.into_edges().into_iter().collect(), 245 | BoundedPolygon::N4(tetragon) => tetragon.into_edges().into_iter().collect(), 246 | } 247 | } 248 | } 249 | 250 | impl IntoEdges for UnboundedPolygon 251 | where 252 | T: Clone, 253 | { 254 | type Output = Vec>; 255 | 256 | fn into_edges(self) -> Self::Output { 257 | self.into_iter() 258 | .perimeter() 259 | .map(|(a, b)| Edge::new(a, b)) 260 | .collect() 261 | } 262 | } 263 | 264 | impl IntoTrigons for Trigon { 265 | type Output = ArrayVec, 1>; 266 | 267 | fn into_trigons(self) -> Self::Output { 268 | ArrayVec::from([self]) 269 | } 270 | } 271 | 272 | impl IntoTrigons for Tetragon 273 | where 274 | T: Clone, 275 | { 276 | type Output = ArrayVec, 2>; 277 | 278 | fn into_trigons(self) -> Self::Output { 279 | let [a, b, c, d] = self.into_array(); 280 | ArrayVec::from([Trigon::new(a.clone(), b, c.clone()), Trigon::new(c, d, a)]) 281 | } 282 | } 283 | 284 | impl IntoTrigons for BoundedPolygon 285 | where 286 | T: Clone, 287 | { 288 | type Output = Vec>; 289 | 290 | fn into_trigons(self) -> Self::Output { 291 | match self { 292 | BoundedPolygon::N3(trigon) => trigon.into_trigons().into_iter().collect(), 293 | BoundedPolygon::N4(tetragon) => tetragon.into_trigons().into_iter().collect(), 294 | } 295 | } 296 | } 297 | 298 | impl IntoSubdivisions for Trigon 299 | where 300 | T: Clone + Interpolate, 301 | { 302 | type Output = ArrayVec, 2>; 303 | 304 | fn into_subdivisions(self) -> Self::Output { 305 | let [a, b, c] = self.into_array(); 306 | let ac = a.clone().midpoint(c.clone()); 307 | ArrayVec::from([Trigon::new(b.clone(), ac.clone(), a), Trigon::new(c, ac, b)]) 308 | } 309 | } 310 | 311 | impl IntoSubdivisions for Tetragon 312 | where 313 | T: Clone + Interpolate, 314 | { 315 | type Output = ArrayVec, 4>; 316 | 317 | fn into_subdivisions(self) -> Self::Output { 318 | let [a, b, c, d] = self.into_array(); 319 | let ab = a.clone().midpoint(b.clone()); 320 | let bc = b.clone().midpoint(c.clone()); 321 | let cd = c.clone().midpoint(d.clone()); 322 | let da = d.clone().midpoint(a.clone()); 323 | let ac = a.clone().midpoint(c.clone()); // Diagonal. 324 | ArrayVec::from([ 325 | Tetragon::new(a, ab.clone(), ac.clone(), da.clone()), 326 | Tetragon::new(ab, b, bc.clone(), ac.clone()), 327 | Tetragon::new(ac.clone(), bc, c, cd.clone()), 328 | Tetragon::new(da, ac, cd, d), 329 | ]) 330 | } 331 | } 332 | 333 | impl IntoTetrahedrons for Tetragon 334 | where 335 | T: Clone + Interpolate, 336 | { 337 | fn into_tetrahedrons(self) -> ArrayVec, 4> { 338 | let [a, b, c, d] = self.into_array(); 339 | let ac = a.clone().midpoint(c.clone()); // Diagonal. 340 | ArrayVec::from([ 341 | Trigon::new(a.clone(), b.clone(), ac.clone()), 342 | Trigon::new(b, c.clone(), ac.clone()), 343 | Trigon::new(c, d.clone(), ac.clone()), 344 | Trigon::new(d, a, ac), 345 | ]) 346 | } 347 | } 348 | 349 | impl IntoSubdivisions for BoundedPolygon 350 | where 351 | T: Clone + Interpolate, 352 | { 353 | type Output = Vec; 354 | 355 | fn into_subdivisions(self) -> Self::Output { 356 | match self { 357 | BoundedPolygon::N3(trigon) => trigon 358 | .into_subdivisions() 359 | .into_iter() 360 | .map(|trigon| trigon.into()) 361 | .collect(), 362 | BoundedPolygon::N4(tetragon) => tetragon 363 | .into_subdivisions() 364 | .into_iter() 365 | .map(|tetragon| tetragon.into()) 366 | .collect(), 367 | } 368 | } 369 | } 370 | 371 | pub trait Vertices

: Sized 372 | where 373 | P: IntoVertices, 374 | { 375 | fn vertices(self) -> Decompose; 376 | } 377 | 378 | impl Vertices

for I 379 | where 380 | I: Iterator, 381 | P: IntoVertices, 382 | { 383 | fn vertices(self) -> Decompose { 384 | Decompose::new(self, P::into_vertices) 385 | } 386 | } 387 | 388 | pub trait Edges

: Sized 389 | where 390 | P: IntoEdges, 391 | { 392 | fn edges(self) -> Decompose, P::Output>; 393 | } 394 | 395 | impl Edges

for I 396 | where 397 | I: Iterator, 398 | P: IntoEdges, 399 | P::Vertex: Clone, 400 | { 401 | fn edges(self) -> Decompose, P::Output> { 402 | Decompose::new(self, P::into_edges) 403 | } 404 | } 405 | 406 | pub trait Triangulate

: Sized 407 | where 408 | P: IntoTrigons, 409 | { 410 | fn triangulate(self) -> Decompose, P::Output>; 411 | } 412 | 413 | impl Triangulate

for I 414 | where 415 | I: Iterator, 416 | P: IntoTrigons, 417 | { 418 | fn triangulate(self) -> Decompose, P::Output> { 419 | Decompose::new(self, P::into_trigons) 420 | } 421 | } 422 | 423 | pub trait Subdivide

: Sized 424 | where 425 | P: IntoSubdivisions, 426 | { 427 | fn subdivide(self) -> Decompose; 428 | } 429 | 430 | impl Subdivide

for I 431 | where 432 | I: Iterator, 433 | P: IntoSubdivisions, 434 | { 435 | fn subdivide(self) -> Decompose { 436 | Decompose::new(self, P::into_subdivisions) 437 | } 438 | } 439 | 440 | pub trait Tetrahedrons: Sized { 441 | fn tetrahedrons(self) -> Decompose, Trigon, ArrayVec, 4>>; 442 | } 443 | 444 | impl Tetrahedrons for I 445 | where 446 | I: Iterator>, 447 | T: Clone + Interpolate, 448 | { 449 | fn tetrahedrons(self) -> Decompose, Trigon, ArrayVec, 4>> { 450 | Decompose::new(self, Tetragon::into_tetrahedrons) 451 | } 452 | } 453 | 454 | fn remap(n: usize, ngons: I, f: F) -> Vec

455 | where 456 | I: IntoIterator, 457 | R: IntoIterator, 458 | F: Fn(P) -> R, 459 | { 460 | let mut ngons: Vec<_> = ngons.into_iter().collect(); 461 | for _ in 0..n { 462 | ngons = ngons.into_iter().flat_map(&f).collect(); 463 | } 464 | ngons 465 | } 466 | -------------------------------------------------------------------------------- /plexus/src/primitive/generate.rs: -------------------------------------------------------------------------------- 1 | //! Polytope generation. 2 | //! 3 | //! This module provides a generic iterator and traits for generating polygons 4 | //! and vertices containing geometric attributes of polytopes like cubes and 5 | //! spheres. The [`Generate`] iterator can be used in iterator expressions. 6 | //! 7 | //! The primary API of this module is provided by the [`Generator`] trait, which 8 | //! is implemented by polytope types like [`Cube`] and [`UvSphere`]. 9 | //! 10 | //! # Examples 11 | //! 12 | //! Generating [raw buffers][`buffer`] from the positional data of a 13 | //! [$uv$-sphere][`UvSphere`]: 14 | //! 15 | //! ```rust 16 | //! # extern crate nalgebra; 17 | //! # extern crate plexus; 18 | //! # 19 | //! use nalgebra::Point3; 20 | //! use plexus::prelude::*; 21 | //! use plexus::primitive::generate::Position; 22 | //! use plexus::primitive::sphere::UvSphere; 23 | //! 24 | //! let sphere = UvSphere::new(16, 16); 25 | //! 26 | //! // Generate the unique set of positional vertices. 27 | //! let positions = sphere 28 | //! .vertices::>>() 29 | //! .collect::>(); 30 | //! 31 | //! // Generate polygons that index the unique set of positional vertices. The 32 | //! // polygons are decomposed into triangles and then into vertices, where each 33 | //! // vertex is an index into the position data. 34 | //! let indices = sphere 35 | //! .indexing_polygons::() 36 | //! .triangulate() 37 | //! .vertices() 38 | //! .collect::>(); 39 | //! ``` 40 | //! 41 | //! [`buffer`]: crate::buffer 42 | //! [`Cube`]: crate::primitive::cube::Cube 43 | //! [`Generate`]: crate::primitive::generate::Generate 44 | //! [`Generator`]: crate::primitive::generate::Generator 45 | //! [`UvSphere`]: crate::primitive::sphere::UvSphere 46 | 47 | use std::marker::PhantomData; 48 | use std::ops::Range; 49 | 50 | use crate::primitive::Polygonal; 51 | 52 | /// Geometric attribute. 53 | /// 54 | /// Types implementing this trait can be used with [`Generator`] to query 55 | /// geometric attributes. For example, the [`Position`] type can be used to get 56 | /// positional data for cubes or spheres via [`Cube`] and [`UvSphere`]. 57 | /// 58 | /// [`Cube`]: crate::primitive::cube::Cube 59 | /// [`Generator`]: crate::primitive::generate::Generator 60 | /// [`Position`]: crate::primitive::generate::Position 61 | /// [`UvSphere`]: crate::primitive::sphere::UvSphere 62 | pub trait Attribute {} 63 | 64 | /// Meta-attribute for surface normals. 65 | /// 66 | /// Describes the surface normals of a polytope. The generated data is derived 67 | /// from the type parameter `S`, which typically requires [`EuclideanSpace`]. 68 | /// 69 | /// # Examples 70 | /// 71 | /// Generating raw buffers with normal data of a [$uv$-sphere][`UvSphere`]: 72 | /// 73 | /// ```rust 74 | /// # extern crate decorum; 75 | /// # extern crate nalgebra; 76 | /// # extern crate plexus; 77 | /// # 78 | /// use decorum::R64; 79 | /// use nalgebra::Point3; 80 | /// use plexus::index::{Flat3, HashIndexer}; 81 | /// use plexus::prelude::*; 82 | /// use plexus::primitive::generate::Normal; 83 | /// use plexus::primitive::sphere::UvSphere; 84 | /// 85 | /// let (indices, normals) = UvSphere::new(8, 8) 86 | /// .polygons::>>() 87 | /// .map_vertices(|normal| normal.into_inner()) 88 | /// .triangulate() 89 | /// .index_vertices::(HashIndexer::default()); 90 | /// ``` 91 | /// 92 | /// [`EuclideanSpace`]: theon::space::EuclideanSpace 93 | /// [`UvSphere`]: crate::primitive::sphere::UvSphere 94 | pub struct Normal { 95 | phantom: PhantomData S>, 96 | } 97 | 98 | impl Attribute for Normal {} 99 | 100 | /// Meta-attribute for positions. 101 | /// 102 | /// Describes the position of vertices in a polytope. The generated data is 103 | /// derived from the type parameter `S`, which typically requires 104 | /// [`EuclideanSpace`]. 105 | /// 106 | /// # Examples 107 | /// 108 | /// Generating raw buffers with positional data of a [cube][`Cube`]: 109 | /// 110 | /// ```rust 111 | /// # extern crate decorum; 112 | /// # extern crate nalgebra; 113 | /// # extern crate plexus; 114 | /// # 115 | /// use decorum::R64; 116 | /// use nalgebra::Point3; 117 | /// use plexus::index::{Flat3, HashIndexer}; 118 | /// use plexus::prelude::*; 119 | /// use plexus::primitive::cube::Cube; 120 | /// use plexus::primitive::generate::Position; 121 | /// use plexus::primitive::UnboundedPolygon; 122 | /// 123 | /// let (indices, positions) = Cube::new() 124 | /// .polygons::>>() 125 | /// .triangulate() 126 | /// .index_vertices::, _>(HashIndexer::default()); 127 | /// ``` 128 | /// 129 | /// [`EuclideanSpace`]: theon::space::EuclideanSpace 130 | /// [`Cube`]: crate::primitive::cube::Cube 131 | /// [`UvSphere`]: crate::primitive::sphere::UvSphere 132 | pub struct Position { 133 | phantom: PhantomData S>, 134 | } 135 | 136 | impl Attribute for Position {} 137 | 138 | /// Iterator that generates topology and geometric attributes. 139 | pub struct Generate<'a, G, S, P> 140 | where 141 | G: 'a, 142 | { 143 | generator: &'a G, 144 | state: S, 145 | range: Range, 146 | f: fn(&'a G, &S, usize) -> P, 147 | } 148 | 149 | impl<'a, G, S, P> Generate<'a, G, S, P> 150 | where 151 | G: 'a, 152 | { 153 | fn new(generator: &'a G, state: S, n: usize, f: fn(&'a G, &S, usize) -> P) -> Self { 154 | Generate { 155 | generator, 156 | state, 157 | range: 0..n, 158 | f, 159 | } 160 | } 161 | } 162 | 163 | impl<'a, G, S, P> Iterator for Generate<'a, G, S, P> 164 | where 165 | G: 'a, 166 | { 167 | type Item = P; 168 | 169 | fn next(&mut self) -> Option { 170 | self.range 171 | .next() 172 | .map(|index| (self.f)(self.generator, &self.state, index)) 173 | } 174 | 175 | fn size_hint(&self) -> (usize, Option) { 176 | self.range.size_hint() 177 | } 178 | } 179 | 180 | pub trait PolygonGenerator { 181 | fn polygon_count(&self) -> usize; 182 | } 183 | 184 | pub trait AttributeGenerator 185 | where 186 | A: Attribute, 187 | { 188 | type State: Default; 189 | } 190 | 191 | pub trait AttributePolygonGenerator: AttributeGenerator + PolygonGenerator 192 | where 193 | A: Attribute, 194 | { 195 | type Output: Polygonal; 196 | 197 | fn polygon_from(&self, state: &Self::State, index: usize) -> Self::Output; 198 | } 199 | 200 | pub trait AttributeVertexGenerator: AttributeGenerator 201 | where 202 | A: Attribute, 203 | { 204 | type Output; 205 | 206 | fn vertex_count(&self) -> usize; 207 | 208 | fn vertex_from(&self, state: &Self::State, index: usize) -> Self::Output; 209 | } 210 | 211 | pub trait IndexingPolygonGenerator: PolygonGenerator 212 | where 213 | A: Attribute, 214 | { 215 | type Output: Polygonal; 216 | 217 | fn indexing_polygon(&self, index: usize) -> Self::Output; 218 | } 219 | 220 | /// Functions for iterating over the topology and geometry of polytopes. 221 | pub trait Generator: Sized { 222 | /// Gets an iterator over the set of **unique** vertices with the given 223 | /// attribute data. 224 | /// 225 | /// Each geometric attribute has an independent set of unique values. For 226 | /// example, [`Cube`] generates six unique surface normals and eight unique 227 | /// positions. 228 | /// 229 | /// This can be paired with the 230 | /// [`indexing_polygons`][`Generator::indexing_polygons`] function to index 231 | /// the set of vertices. 232 | /// 233 | /// # Examples 234 | /// 235 | /// ```rust 236 | /// # extern crate nalgebra; 237 | /// # extern crate plexus; 238 | /// # 239 | /// use nalgebra::Point3; 240 | /// use plexus::prelude::*; 241 | /// use plexus::primitive::cube::Cube; 242 | /// use plexus::primitive::generate::Position; 243 | /// 244 | /// type E3 = Point3; 245 | /// 246 | /// let cube = Cube::new(); 247 | /// 248 | /// let positions = cube.vertices::>().collect::>(); 249 | /// let indices = cube 250 | /// .indexing_polygons::() 251 | /// .triangulate() 252 | /// .vertices() 253 | /// .collect::>(); 254 | /// ``` 255 | /// 256 | /// [`Cube`]: crate::primitive::cube::Cube 257 | /// [`Generator::indexing_polygons`]: crate::primitive::generate::Generator::indexing_polygons 258 | fn vertices( 259 | &self, 260 | ) -> Generate>::Output> 261 | where 262 | Self: AttributeVertexGenerator, 263 | A: Attribute, 264 | { 265 | self.vertices_from(Default::default()) 266 | } 267 | 268 | fn vertices_from( 269 | &self, 270 | state: Self::State, 271 | ) -> Generate>::Output> 272 | where 273 | Self: AttributeVertexGenerator, 274 | A: Attribute, 275 | { 276 | Generate::new(self, state, self.vertex_count(), Self::vertex_from) 277 | } 278 | 279 | /// Gets an iterator over the set of polygons with the given attribute data. 280 | /// 281 | /// # Examples 282 | /// 283 | /// ```rust 284 | /// # extern crate decorum; 285 | /// # extern crate nalgebra; 286 | /// # extern crate plexus; 287 | /// # 288 | /// use decorum::R64; 289 | /// use nalgebra::Point3; 290 | /// use plexus::index::HashIndexer; 291 | /// use plexus::prelude::*; 292 | /// use plexus::primitive::cube::Cube; 293 | /// use plexus::primitive::generate::Position; 294 | /// use plexus::primitive::Tetragon; 295 | /// 296 | /// let (indices, positions) = Cube::new() 297 | /// .polygons::>>() 298 | /// .index_vertices::, _>(HashIndexer::default()); 299 | /// ``` 300 | fn polygons( 301 | &self, 302 | ) -> Generate>::Output> 303 | where 304 | Self: AttributePolygonGenerator, 305 | A: Attribute, 306 | { 307 | self.polygons_from(Default::default()) 308 | } 309 | 310 | fn polygons_from( 311 | &self, 312 | state: Self::State, 313 | ) -> Generate>::Output> 314 | where 315 | Self: AttributePolygonGenerator, 316 | A: Attribute, 317 | { 318 | Generate::new(self, state, self.polygon_count(), Self::polygon_from) 319 | } 320 | 321 | /// Gets an iterator over a set of polygons that index the unique set of 322 | /// vertices with the given attribute. 323 | /// 324 | /// Indexing differs per geometric attribute, because each attribute has an 325 | /// independent set of unique values. For example, [`Cube`] generates six 326 | /// unique surface normals and eight unique positions. 327 | /// 328 | /// When used with meta-attribute types like [`Position`], input types are 329 | /// not needed and default type parameters can be used instead. For example, 330 | /// if `Position>` is used to generate positional data, then 331 | /// `Position<()>` (or `Position`) can be used to generate indexing 332 | /// polygons. 333 | /// 334 | /// # Examples 335 | /// 336 | /// ```rust 337 | /// # extern crate nalgebra; 338 | /// # extern crate plexus; 339 | /// # 340 | /// use nalgebra::Point3; 341 | /// use plexus::buffer::MeshBuffer4; 342 | /// use plexus::prelude::*; 343 | /// use plexus::primitive::cube::Cube; 344 | /// use plexus::primitive::generate::Position; 345 | /// 346 | /// type E3 = Point3; 347 | /// 348 | /// let cube = Cube::new(); 349 | /// let buffer = MeshBuffer4::::from_raw_buffers( 350 | /// cube.indexing_polygons::(), 351 | /// cube.vertices::>(), 352 | /// ); 353 | /// ``` 354 | /// 355 | /// [`Cube`]: crate::primitive::cube::Cube 356 | /// [`Position`]: crate::primitive::generate::Position 357 | fn indexing_polygons(&self) -> Generate 358 | where 359 | Self: IndexingPolygonGenerator, 360 | A: Attribute, 361 | { 362 | Generate::new(self, (), self.polygon_count(), |generator, _, index| { 363 | generator.indexing_polygon(index) 364 | }) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /plexus/src/primitive/sphere.rs: -------------------------------------------------------------------------------- 1 | //! Sphere primitives. 2 | //! 3 | //! # Examples 4 | //! 5 | //! Generating a graph from the positional data of a $uv$-sphere. 6 | //! 7 | //! ```rust 8 | //! # extern crate decorum; 9 | //! # extern crate nalgebra; 10 | //! # extern crate plexus; 11 | //! # 12 | //! use decorum::R64; 13 | //! use nalgebra::Point3; 14 | //! use plexus::graph::MeshGraph; 15 | //! use plexus::index::HashIndexer; 16 | //! use plexus::prelude::*; 17 | //! use plexus::primitive::generate::Position; 18 | //! use plexus::primitive::sphere::UvSphere; 19 | //! 20 | //! type E3 = Point3; 21 | //! 22 | //! let mut graph = UvSphere::new(16, 8) 23 | //! .polygons::>() 24 | //! .collect_with_indexer::, _>(HashIndexer::default()) 25 | //! .unwrap(); 26 | //! ``` 27 | 28 | use num::traits::real::Real; 29 | use num::traits::FloatConst; 30 | use num::{NumCast, One, ToPrimitive}; 31 | use std::cmp; 32 | use theon::adjunct::Map; 33 | use theon::query::Unit; 34 | use theon::space::{EuclideanSpace, FiniteDimensional, Scalar, Vector}; 35 | use typenum::U3; 36 | 37 | use crate::primitive::generate::{ 38 | AttributeGenerator, AttributePolygonGenerator, AttributeVertexGenerator, Generator, 39 | IndexingPolygonGenerator, Normal, PolygonGenerator, Position, 40 | }; 41 | use crate::primitive::{BoundedPolygon, Tetragon, Trigon}; 42 | 43 | #[derive(Clone, Copy)] 44 | pub struct Bounds 45 | where 46 | S: EuclideanSpace, 47 | { 48 | radius: Scalar, 49 | } 50 | 51 | impl Bounds 52 | where 53 | S: EuclideanSpace, 54 | { 55 | pub fn with_radius(radius: Scalar) -> Self { 56 | Bounds { radius } 57 | } 58 | 59 | pub fn with_width(width: Scalar) -> Self { 60 | Self::with_radius(width / (Scalar::::one() + One::one())) 61 | } 62 | 63 | pub fn unit_radius() -> Self { 64 | Self::with_radius(One::one()) 65 | } 66 | 67 | pub fn unit_width() -> Self { 68 | Self::with_width(One::one()) 69 | } 70 | } 71 | 72 | impl Default for Bounds 73 | where 74 | S: EuclideanSpace, 75 | { 76 | fn default() -> Self { 77 | Self::unit_radius() 78 | } 79 | } 80 | 81 | #[derive(Clone, Copy)] 82 | pub struct UvSphere { 83 | nu: usize, // Meridians. 84 | nv: usize, // Parallels. 85 | } 86 | 87 | impl UvSphere { 88 | pub fn new(nu: usize, nv: usize) -> Self { 89 | UvSphere { 90 | nu: cmp::max(3, nu), 91 | nv: cmp::max(2, nv), 92 | } 93 | } 94 | 95 | fn vertex_with_position_from( 96 | &self, 97 | state: &>>::State, 98 | u: usize, 99 | v: usize, 100 | ) -> S 101 | where 102 | Self: AttributeGenerator, State = Bounds>, 103 | S: EuclideanSpace + FiniteDimensional, 104 | Scalar: FloatConst, 105 | { 106 | let one = Scalar::::one(); 107 | let pi = FloatConst::PI(); 108 | let u = (into_scalar::<_, S>(u) / into_scalar::<_, S>(self.nu)) * pi * (one + one); 109 | let v = (into_scalar::<_, S>(v) / into_scalar::<_, S>(self.nv)) * pi; 110 | S::from_xyz( 111 | state.radius * u.cos() * v.sin(), 112 | state.radius * u.sin() * v.sin(), 113 | state.radius * v.cos(), 114 | ) 115 | } 116 | 117 | fn index_for_position(&self, u: usize, v: usize) -> usize { 118 | if v == 0 { 119 | 0 120 | } 121 | else if v == self.nv { 122 | ((self.nv - 1) * self.nu) + 1 123 | } 124 | else { 125 | ((v - 1) * self.nu) + (u % self.nu) + 1 126 | } 127 | } 128 | 129 | fn map_polygon_index(&self, index: usize) -> (usize, usize) { 130 | (index % self.nu, index / self.nu) 131 | } 132 | } 133 | 134 | impl Default for UvSphere { 135 | fn default() -> Self { 136 | UvSphere::new(16, 16) 137 | } 138 | } 139 | 140 | impl PolygonGenerator for UvSphere { 141 | fn polygon_count(&self) -> usize { 142 | self.nu * self.nv 143 | } 144 | } 145 | 146 | impl AttributeGenerator> for UvSphere 147 | where 148 | S: EuclideanSpace + FiniteDimensional, 149 | { 150 | type State = (); 151 | } 152 | 153 | impl AttributeVertexGenerator> for UvSphere 154 | where 155 | S: EuclideanSpace + FiniteDimensional, 156 | Scalar: FloatConst, 157 | { 158 | type Output = Unit>; 159 | 160 | fn vertex_count(&self) -> usize { 161 | (self.nv - 1) * self.nu + 2 162 | } 163 | 164 | fn vertex_from(&self, _: &Self::State, index: usize) -> Self::Output { 165 | let position = 166 | AttributeVertexGenerator::>::vertex_from(self, &Default::default(), index); 167 | Unit::try_from_inner(position.into_coordinates()).expect("non-zero vector") 168 | } 169 | } 170 | 171 | impl AttributePolygonGenerator> for UvSphere 172 | where 173 | S: EuclideanSpace + FiniteDimensional, 174 | Scalar: FloatConst, 175 | { 176 | type Output = BoundedPolygon>>; 177 | 178 | fn polygon_from(&self, _: &Self::State, index: usize) -> Self::Output { 179 | AttributePolygonGenerator::>::polygon_from(self, &Default::default(), index) 180 | .map(|position| { 181 | Unit::try_from_inner(position.into_coordinates()).expect("non-zero vector") 182 | }) 183 | } 184 | } 185 | 186 | impl IndexingPolygonGenerator> for UvSphere { 187 | type Output = BoundedPolygon; 188 | 189 | fn indexing_polygon(&self, index: usize) -> Self::Output { 190 | IndexingPolygonGenerator::>::indexing_polygon(self, index) 191 | } 192 | } 193 | 194 | impl AttributeGenerator> for UvSphere 195 | where 196 | S: EuclideanSpace + FiniteDimensional, 197 | { 198 | type State = Bounds; 199 | } 200 | 201 | impl AttributeVertexGenerator> for UvSphere 202 | where 203 | S: EuclideanSpace + FiniteDimensional, 204 | Scalar: FloatConst, 205 | { 206 | type Output = S; 207 | 208 | fn vertex_count(&self) -> usize { 209 | (self.nv - 1) * self.nu + 2 210 | } 211 | 212 | fn vertex_from(&self, state: &Self::State, index: usize) -> Self::Output { 213 | let count = AttributeVertexGenerator::>::vertex_count(self); 214 | if index == 0 { 215 | self.vertex_with_position_from::(state, 0, 0) 216 | } 217 | else if index == (count - 1) { 218 | self.vertex_with_position_from::(state, 0, self.nv) 219 | } 220 | else { 221 | let index = index - 1; 222 | self.vertex_with_position_from::(state, index % self.nu, (index / self.nu) + 1) 223 | } 224 | } 225 | } 226 | 227 | impl AttributePolygonGenerator> for UvSphere 228 | where 229 | S: EuclideanSpace + FiniteDimensional, 230 | Scalar: FloatConst, 231 | { 232 | type Output = BoundedPolygon; 233 | 234 | fn polygon_from(&self, state: &Self::State, index: usize) -> Self::Output { 235 | // Prevent floating point rounding errors by wrapping the incremented 236 | // values for `(u, v)` into `(p, q)`. This is important for indexing 237 | // geometry, because small differences in the computation of spatial 238 | // vertices will produce redundant output vertices. There should be 239 | // exactly `(nv - 1) * nu + 2` unique values of `(u, v)` used to 240 | // generate positions. 241 | // 242 | // There are two important observations: 243 | // 244 | // 1. `u` must wrap, but `v` need not. There are `nu` meridians of 245 | // points and polygons, but there are `nv` parallels of polygons 246 | // and `nv + 1` parallels of points. 247 | // 2. `u`, which represents a meridian, is meaningless at the poles, 248 | // and can be normalized to zero. 249 | let (u, v) = self.map_polygon_index(index); 250 | let (p, q) = ((u + 1) % self.nu, v + 1); 251 | 252 | // Generate the vertices at the requested meridian and parallel. The 253 | // lower bound of `(u, v)` is always used, so compute that in advance 254 | // (`lower`). Emit triangles at the poles, otherwise quadrilaterals. 255 | let lower = self.vertex_with_position_from(state, u, v); 256 | if v == 0 { 257 | Trigon::new( 258 | lower, 259 | self.vertex_with_position_from(state, u, q), 260 | self.vertex_with_position_from(state, p, q), 261 | ) 262 | .into() 263 | } 264 | else if v == self.nv - 1 { 265 | Trigon::new( 266 | // Normalize `u` at the pole, using `(0, nv)` in place of 267 | // `(p, q)`. 268 | self.vertex_with_position_from(state, 0, self.nv), 269 | self.vertex_with_position_from(state, p, v), 270 | lower, 271 | ) 272 | .into() 273 | } 274 | else { 275 | Tetragon::new( 276 | lower, 277 | self.vertex_with_position_from(state, u, q), 278 | self.vertex_with_position_from(state, p, q), 279 | self.vertex_with_position_from(state, p, v), 280 | ) 281 | .into() 282 | } 283 | } 284 | } 285 | 286 | impl IndexingPolygonGenerator> for UvSphere { 287 | type Output = BoundedPolygon; 288 | 289 | fn indexing_polygon(&self, index: usize) -> Self::Output { 290 | let (u, v) = self.map_polygon_index(index); 291 | let (p, q) = (u + 1, v + 1); 292 | 293 | let low = self.index_for_position(u, v); 294 | let high = self.index_for_position(p, q); 295 | if v == 0 { 296 | Trigon::new(low, self.index_for_position(u, q), high).into() 297 | } 298 | else if v == self.nv - 1 { 299 | Trigon::new(high, self.index_for_position(p, v), low).into() 300 | } 301 | else { 302 | Tetragon::new( 303 | low, 304 | self.index_for_position(u, q), 305 | high, 306 | self.index_for_position(p, v), 307 | ) 308 | .into() 309 | } 310 | } 311 | } 312 | 313 | impl Generator for UvSphere {} 314 | 315 | fn into_scalar(value: T) -> Scalar 316 | where 317 | T: ToPrimitive, 318 | S: EuclideanSpace, 319 | { 320 | as NumCast>::from(value).unwrap() 321 | } 322 | 323 | #[cfg(test)] 324 | mod tests { 325 | use nalgebra::Point3; 326 | use std::collections::BTreeSet; 327 | 328 | use crate::prelude::*; 329 | use crate::primitive::generate::Position; 330 | use crate::primitive::sphere::UvSphere; 331 | 332 | type E3 = Point3; 333 | 334 | #[test] 335 | fn vertex_count() { 336 | assert_eq!( 337 | 5, 338 | UvSphere::new(3, 2) 339 | .vertices::>() // 5 conjoint vertices. 340 | .count() 341 | ); 342 | } 343 | 344 | #[test] 345 | fn polygon_vertex_count() { 346 | assert_eq!( 347 | 18, 348 | UvSphere::new(3, 2) 349 | .polygons::>() // 6 triangles, 18 vertices. 350 | .vertices() 351 | .count() 352 | ); 353 | } 354 | 355 | #[test] 356 | fn position_index_to_vertex_mapping() { 357 | assert_eq!( 358 | 5, 359 | UvSphere::new(3, 2) 360 | .indexing_polygons::() // 18 vertices, 5 indices. 361 | .vertices() 362 | .collect::>() 363 | .len() 364 | ) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /plexus/src/transact.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::mem; 3 | 4 | pub trait Transact: Sized { 5 | type Commit; 6 | type Abort; 7 | type Error: Debug; 8 | 9 | fn commit(self) -> Result; 10 | 11 | // LINT: This is indeed a complex type, but refactoring into a type definition cannot be done 12 | // trivially (and may not reduce complexity). 13 | #[expect(clippy::type_complexity)] 14 | fn commit_with(mut self, f: F) -> Result<(Self::Commit, U), (Self::Abort, Self::Error)> 15 | where 16 | F: FnOnce(&mut Self) -> Result, 17 | E: Into, 18 | { 19 | match f(&mut self) { 20 | Ok(value) => self.commit().map(|output| (output, value)), 21 | Err(error) => Err((self.abort(), error.into())), 22 | } 23 | } 24 | 25 | fn abort(self) -> Self::Abort; 26 | } 27 | 28 | pub trait Bypass: Transact { 29 | fn bypass(self) -> Self::Commit; 30 | } 31 | 32 | pub trait BypassOrCommit: Bypass { 33 | fn bypass_or_commit(self) -> Result; 34 | 35 | // LINT: This is indeed a complex type, but refactoring into a type definition cannot be done 36 | // trivially (and may not reduce complexity). 37 | #[expect(clippy::type_complexity)] 38 | fn bypass_or_commit_with( 39 | self, 40 | f: F, 41 | ) -> Result<(Self::Commit, X), (Self::Abort, Self::Error)> 42 | where 43 | F: FnOnce(&mut Self) -> Result, 44 | E: Into; 45 | } 46 | 47 | #[cfg(test)] 48 | impl BypassOrCommit for T 49 | where 50 | T: Bypass, 51 | { 52 | fn bypass_or_commit(self) -> Result { 53 | self.commit() 54 | } 55 | 56 | fn bypass_or_commit_with(self, f: F) -> Result<(T::Commit, X), (T::Abort, T::Error)> 57 | where 58 | F: FnOnce(&mut T) -> Result, 59 | E: Into, 60 | { 61 | self.commit_with(f) 62 | } 63 | } 64 | 65 | #[cfg(not(test))] 66 | impl BypassOrCommit for T 67 | where 68 | T: Bypass, 69 | { 70 | fn bypass_or_commit(self) -> Result { 71 | Ok(self.bypass()) 72 | } 73 | 74 | fn bypass_or_commit_with( 75 | mut self, 76 | f: F, 77 | ) -> Result<(T::Commit, X), (T::Abort, T::Error)> 78 | where 79 | F: FnOnce(&mut T) -> Result, 80 | E: Into, 81 | { 82 | match f(&mut self) { 83 | Ok(value) => Ok((self.bypass(), value)), 84 | Err(error) => Err((self.abort(), error.into())), 85 | } 86 | } 87 | } 88 | 89 | pub trait Mutate: Transact { 90 | fn replace(target: &mut T, replacement: T) -> Swapped 91 | where 92 | Self: From + Transact, 93 | { 94 | Swapped::replace(target, replacement) 95 | } 96 | 97 | fn take(target: &mut T) -> Swapped 98 | where 99 | Self: From + Transact, 100 | T: Default, 101 | { 102 | Swapped::take(target) 103 | } 104 | } 105 | 106 | impl Mutate for T where T: Transact {} 107 | 108 | pub trait ClosedInput: Transact<::Input> { 109 | type Input; 110 | } 111 | 112 | trait Drain { 113 | fn as_option_mut(&mut self) -> &mut Option; 114 | 115 | fn drain(&mut self) -> T { 116 | self.as_option_mut().take().expect("drained") 117 | } 118 | 119 | fn undrain(&mut self, value: T) { 120 | let drained = self.as_option_mut(); 121 | if drained.is_some() { 122 | panic!("undrained"); 123 | } 124 | else { 125 | *drained = Some(value); 126 | } 127 | } 128 | 129 | fn try_swap_or(&mut self, value: T, mut f: F) -> Result 130 | where 131 | F: FnMut(T) -> Result<(T, U), E>, 132 | { 133 | match f(self.drain()) { 134 | Ok((value, output)) => { 135 | self.undrain(value); 136 | Ok(output) 137 | } 138 | Err(error) => { 139 | self.undrain(value); 140 | Err(error) 141 | } 142 | } 143 | } 144 | } 145 | 146 | pub struct Swapped<'a, T, M> 147 | where 148 | M: From + Mutate, 149 | { 150 | inner: Option<(&'a mut T, M)>, 151 | } 152 | 153 | impl<'a, T, M> Swapped<'a, T, M> 154 | where 155 | M: From + Mutate, 156 | { 157 | pub fn replace(target: &'a mut T, replacement: T) -> Self { 158 | let mutant = mem::replace(target, replacement); 159 | Swapped { 160 | inner: Some((target, M::from(mutant))), 161 | } 162 | } 163 | 164 | pub fn take(target: &'a mut T) -> Self 165 | where 166 | T: Default, 167 | { 168 | Swapped::replace(target, T::default()) 169 | } 170 | 171 | fn drain_and_commit( 172 | &mut self, 173 | ) -> Result<&'a mut T, (&'a mut T, >::Error)> { 174 | let (target, inner) = self.drain(); 175 | match inner.commit() { 176 | Ok(mutant) => { 177 | *target = mutant; 178 | Ok(target) 179 | } 180 | Err((_, error)) => Err((target, error)), 181 | } 182 | } 183 | 184 | fn drain_and_abort(&mut self) -> &'a mut T { 185 | let (target, inner) = self.drain(); 186 | inner.abort(); 187 | target 188 | } 189 | } 190 | 191 | impl<'a, T, M> Swapped<'a, T, M> 192 | where 193 | M: Bypass + From + Mutate, 194 | { 195 | pub fn drain_and_bypass(&mut self) -> &'a mut T { 196 | let (target, inner) = self.drain(); 197 | *target = inner.bypass(); 198 | target 199 | } 200 | } 201 | 202 | impl AsRef for Swapped<'_, T, M> 203 | where 204 | M: From + Mutate, 205 | { 206 | fn as_ref(&self) -> &M { 207 | &self.inner.as_ref().unwrap().1 208 | } 209 | } 210 | 211 | impl AsMut for Swapped<'_, T, M> 212 | where 213 | M: From + Mutate, 214 | { 215 | fn as_mut(&mut self) -> &mut M { 216 | &mut self.inner.as_mut().unwrap().1 217 | } 218 | } 219 | 220 | impl<'a, T, M> Bypass<&'a mut T> for Swapped<'a, T, M> 221 | where 222 | M: Bypass + From + Mutate, 223 | { 224 | fn bypass(mut self) -> Self::Commit { 225 | let mutant = self.drain_and_bypass(); 226 | mem::forget(self); 227 | mutant 228 | } 229 | } 230 | 231 | impl<'a, T, M> Drain<(&'a mut T, M)> for Swapped<'a, T, M> 232 | where 233 | M: From + Mutate, 234 | { 235 | fn as_option_mut(&mut self) -> &mut Option<(&'a mut T, M)> { 236 | &mut self.inner 237 | } 238 | } 239 | 240 | impl Drop for Swapped<'_, T, M> 241 | where 242 | M: From + Mutate, 243 | { 244 | fn drop(&mut self) { 245 | self.drain_and_abort(); 246 | } 247 | } 248 | 249 | impl<'a, T, M> From<&'a mut T> for Swapped<'a, T, M> 250 | where 251 | T: Default, 252 | M: From + Mutate, 253 | { 254 | fn from(target: &'a mut T) -> Self { 255 | Self::replace(target, Default::default()) 256 | } 257 | } 258 | 259 | impl<'a, T, M> Transact<&'a mut T> for Swapped<'a, T, M> 260 | where 261 | M: From + Mutate, 262 | { 263 | type Commit = &'a mut T; 264 | type Abort = &'a mut T; 265 | type Error = >::Error; 266 | 267 | fn commit(mut self) -> Result { 268 | let mutant = self.drain_and_commit(); 269 | mem::forget(self); 270 | mutant 271 | } 272 | 273 | fn abort(mut self) -> Self::Abort { 274 | let mutant = self.drain_and_abort(); 275 | mem::forget(self); 276 | mutant 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /rustdoc.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # TODO: This script sets the `RUSTDOCFLAGS` environment variable to configure 4 | # the KaTeX header for documentation. This cannot be accomplished with 5 | # Cargo configuration yet. 6 | # 7 | # See https://github.com/rust-lang/cargo/issues/8097 8 | 9 | set -e 10 | 11 | RUSTDOCFLAGS=--html-in-header=$(realpath ./doc/katex-header.html) \ 12 | cargo +nightly doc $@ 13 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | control_brace_style = "ClosingNextLine" 2 | format_code_in_doc_comments = true 3 | --------------------------------------------------------------------------------