├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── .gitignore ├── Cochineal-OFL.txt ├── Cochineal-Roman.otf ├── fps_boxplot.svg └── fps_vs_render.svg ├── benches ├── profile_triangulation.rs └── triangulation.rs ├── doc ├── images │ └── demo.png └── start.md ├── examples └── box.rs ├── playground ├── bevy │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── fps_bench │ ├── Cargo.toml │ ├── FPSBench │ │ ├── Manifest.toml │ │ ├── Project.toml │ │ └── src │ │ │ ├── FPSBench.jl │ │ │ └── main.jl │ └── src │ │ └── main.rs └── wgpu │ ├── Cargo.toml │ └── src │ ├── main.rs │ └── shader.wgsl ├── rustfmt.toml └── src ├── extensions ├── bevy │ ├── gizmo │ │ ├── mod.rs │ │ ├── text.rs │ │ └── text3d.rs │ ├── math │ │ ├── mat5.rs │ │ ├── mod.rs │ │ ├── polygon.rs │ │ ├── quat.rs │ │ ├── vec2.rs │ │ ├── vec3.rs │ │ └── vec4.rs │ ├── mesh2d.rs │ ├── mesh3d.rs │ ├── mod.rs │ ├── vertex_payload_2d.rs │ └── vertex_payload_3d.rs ├── mod.rs ├── nalgebra │ ├── default_vertex_payload.rs │ ├── math │ │ ├── mod.rs │ │ ├── polygon.rs │ │ ├── rotate.rs │ │ ├── transform_n.rs │ │ ├── vec2.rs │ │ ├── vec3.rs │ │ ├── vec4.rs │ │ └── vec_n.rs │ ├── mesh2d.rs │ ├── mesh_nd.rs │ └── mod.rs ├── svg │ ├── mod.rs │ └── svg.rs └── wgpu │ └── mod.rs ├── halfedge ├── edge │ ├── basics.rs │ ├── halfedge.rs │ ├── iterator.rs │ └── mod.rs ├── face │ └── mod.rs ├── mesh │ ├── basics.rs │ ├── builder │ │ ├── builder.rs │ │ ├── halfedge.rs │ │ ├── mod.rs │ │ ├── path.rs │ │ ├── semi.rs │ │ ├── subdivision.rs │ │ └── vertex.rs │ ├── check.rs │ ├── halfedge.rs │ ├── mod.rs │ └── pseudo_winged.rs ├── mod.rs ├── primitives │ └── mod.rs └── vertex │ ├── basics.rs │ ├── iterator.rs │ └── mod.rs ├── lib.rs ├── math ├── impls │ ├── f32.rs │ ├── f64.rs │ └── mod.rs ├── index_type.rs ├── line_segment.rs ├── mod.rs ├── polygon.rs ├── position.rs ├── quaternion.rs ├── scalar.rs ├── transform.rs ├── transformable.rs ├── vector.rs ├── vector2d.rs ├── vector3d.rs ├── vector4d.rs └── zero.rs ├── mesh ├── edge │ ├── basics.rs │ ├── curved.rs │ ├── halfedge.rs │ ├── mod.rs │ └── payload.rs ├── face │ ├── basics.rs │ ├── face3d.rs │ ├── mod.rs │ └── payload.rs ├── mesh │ ├── basics.rs │ ├── builder.rs │ ├── check.rs │ ├── fonts.rs │ ├── halfedge.rs │ ├── iso.rs │ ├── mesh_type.rs │ ├── mod.rs │ ├── netsci.rs │ ├── normals.rs │ ├── path_builder.rs │ ├── payload.rs │ ├── position.rs │ ├── topology.rs │ ├── transform.rs │ └── triangulate.rs ├── mod.rs ├── triangulation.rs └── vertex │ ├── basics.rs │ ├── halfedge.rs │ ├── interpolator.rs │ ├── mod.rs │ ├── payload.rs │ └── transform.rs ├── operations ├── extrude.rs ├── loft.rs ├── mod.rs └── subdivision.rs ├── primitives ├── misc.rs ├── mod.rs ├── plane.rs ├── polygon.rs ├── prismatoid.rs └── sphere.rs ├── tesselate ├── convex.rs ├── delaunay.rs ├── ear_clipping.rs ├── fixed_n.rs ├── min_weight_dynamic.rs ├── min_weight_greedy.rs ├── mod.rs └── sweep │ ├── chain.rs │ ├── interval.rs │ ├── mod.rs │ ├── monotone │ ├── delaunay.rs │ ├── dynamic.rs │ ├── linear.rs │ └── mod.rs │ ├── point.rs │ ├── status.rs │ ├── sweep.rs │ └── vertex_type.rs └── util ├── deletable.rs └── mod.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | linker = "rust-lld.exe" # slightly faster than microsofts linker 3 | rustflags = ["-Zshare-generics=n"] -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | #https://github.com/bevyengine/bevy_github_ci_template/tree/main 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | # Run cargo test 16 | test: 17 | name: Test Suite 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | steps: 21 | - name: Checkout sources 22 | uses: actions/checkout@v4 23 | - name: Cache 24 | uses: actions/cache@v4 25 | with: 26 | path: | 27 | ~/.cargo/bin/ 28 | ~/.cargo/registry/index/ 29 | ~/.cargo/registry/cache/ 30 | ~/.cargo/git/db/ 31 | target/ 32 | key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.toml') }} 33 | - name: Install stable toolchain 34 | uses: dtolnay/rust-toolchain@stable 35 | - name: Install Dependencies 36 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev 37 | - name: Run cargo test 38 | run: cargo test --verbose --no-default-features --features="bevy,nalgebra,fonts,svg" 39 | - name: Compile examples 40 | run: cargo build --examples --no-default-features --features="example_deps" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /target-bin 3 | bench_results.jl 4 | bench_results.jl.back 5 | graph.png 6 | flamegraph.svg 7 | *.old 8 | *.data 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "cartesian", 4 | "casteljau", 5 | "delaunay", 6 | "kahan", 7 | "laplacian", 8 | "lerped", 9 | "nalgebra", 10 | "NURBS", 11 | "perp", 12 | "recip", 13 | "Simd", 14 | "undecisive", 15 | "usvg", 16 | "wgpu", 17 | "winit", 18 | "xyzw" 19 | ], 20 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procedural_modelling" 3 | description = "A framework-agnostic Procedural Modelling crate." 4 | version = "0.3.0" 5 | edition = "2021" 6 | categories = ["graphics", "rendering", "game-development"] 7 | keywords = ["gamedev", "graphics", "procedural", "meshes", "modelling"] 8 | homepage = "https://bevy-procedural.org/modelling" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/bevy-procedural/modelling" 12 | documentation = "https://docs.rs/procedural_modelling" 13 | rust-version = "1.80.0" 14 | include = [ 15 | "src/**/*", 16 | "doc/**/*", 17 | "examples/**/*", 18 | "playground/**/*", 19 | "README.md", 20 | "Cargo.toml", 21 | ] 22 | 23 | [lib] 24 | name = "procedural_modelling" 25 | path = "src/lib.rs" 26 | crate-type = ["rlib"] 27 | 28 | [workspace] 29 | members = ["playground/bevy", "playground/wgpu", "playground/fps_bench"] 30 | 31 | [workspace.lints.clippy] 32 | type_complexity = "allow" 33 | doc_markdown = "warn" 34 | manual_let_else = "warn" 35 | undocumented_unsafe_blocks = "warn" 36 | redundant_else = "warn" 37 | match_same_arms = "warn" 38 | semicolon_if_nothing_returned = "warn" 39 | map_flatten = "warn" 40 | 41 | ptr_as_ptr = "warn" 42 | ptr_cast_constness = "warn" 43 | ref_as_ptr = "warn" 44 | 45 | [workspace.lints.rust] 46 | unsafe_op_in_unsafe_fn = "warn" 47 | missing_docs = "warn" 48 | 49 | [lints] 50 | workspace = true 51 | 52 | [dependencies] 53 | bevy = { version = "^0.15.0", default-features = false, optional = true } 54 | itertools = "^0.13.0" 55 | meshopt = { version = "^0.3.0", optional = true } 56 | rand = "^0.8.5" 57 | spade = "^2.12.1" 58 | usvg = { version = "0.44.0", optional = true } 59 | lazy_static = "1.5.0" 60 | ab_glyph = { version = "0.2.29", optional = true } 61 | nalgebra = { version = "0.33.0", optional = true } 62 | num-traits = "0.2.19" 63 | criterion = { version = "0.5.1", features = ["html_reports"], optional = true } 64 | web-sys = "0.3.72" 65 | 66 | [features] 67 | default = ["nalgebra", "netsci", "fonts"] 68 | netsci = ["nalgebra"] 69 | wgpu = ["nalgebra"] 70 | bevy = ["dep:bevy", "bevy/bevy_core_pipeline"] 71 | gizmo = ["bevy", "bevy/bevy_text", "bevy/bevy_ui"] 72 | example_deps = ["bevy", "bevy/default"] 73 | svg = ["dep:usvg"] 74 | nalgebra = ["dep:nalgebra"] 75 | fonts = ["dep:ab_glyph"] 76 | bevy_dynamic = ["bevy/dynamic_linking"] 77 | meshopt = ["dep:meshopt"] 78 | sweep_debug = [] 79 | sweep_debug_print = ["sweep_debug"] 80 | benchmarks = ["dep:criterion", "bevy"] 81 | 82 | 83 | [[example]] 84 | name = "box" 85 | path = "examples/box.rs" 86 | doc-scrape-examples = true 87 | required-features = ["example_deps"] 88 | 89 | [[bench]] 90 | name = "triangulation" 91 | harness = false 92 | required-features = ["benchmarks"] 93 | 94 | # Enable a small amount of optimization in debug mode 95 | [profile.fast-dev] 96 | inherits = "dev" 97 | opt-level = 1 98 | 99 | # Enable high optimizations for dependencies (incl. Bevy), but not for our code: 100 | [profile.fast-dev.package."*"] 101 | inherits = "dev" 102 | opt-level = 3 103 | 104 | [profile.wasm-release] 105 | inherits = "release" 106 | opt-level = "z" 107 | lto = "fat" 108 | codegen-units = 1 109 | 110 | [profile.profiling] 111 | inherits = "release" 112 | debug = true 113 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 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 all 11 | 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 THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !Cochineal-Roman.otf 4 | !Cochineal-OFL.txt 5 | !*.svg -------------------------------------------------------------------------------- /assets/Cochineal-OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014 Sebastian Kosch, with Reserved Font Name Crimson. 2 | 3 | Copyright (c) 2016--2021, Michael Sharpe (msharpe@ucsd.edu), 4 | with Reserved Font Name Cochineal. 5 | 6 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 7 | This license is copied below, and is also available with a FAQ at: 8 | http://scripts.sil.org/OFL 9 | 10 | 11 | ----------------------------------------------------------- 12 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 13 | ----------------------------------------------------------- 14 | 15 | PREAMBLE 16 | The goals of the Open Font License (OFL) are to stimulate worldwide 17 | development of collaborative font projects, to support the font creation 18 | efforts of academic and linguistic communities, and to provide a free and 19 | open framework in which fonts may be shared and improved in partnership 20 | with others. 21 | 22 | The OFL allows the licensed fonts to be used, studied, modified and 23 | redistributed freely as long as they are not sold by themselves. The 24 | fonts, including any derivative works, can be bundled, embedded, 25 | redistributed and/or sold with any software provided that any reserved 26 | names are not used by derivative works. The fonts and derivatives, 27 | however, cannot be released under any other type of license. The 28 | requirement for fonts to remain under this license does not apply 29 | to any document created using the fonts or their derivatives. 30 | 31 | DEFINITIONS 32 | "Font Software" refers to the set of files released by the Copyright 33 | Holder(s) under this license and clearly marked as such. This may 34 | include source files, build scripts and documentation. 35 | 36 | "Reserved Font Name" refers to any names specified as such after the 37 | copyright statement(s). 38 | 39 | "Original Version" refers to the collection of Font Software components as 40 | distributed by the Copyright Holder(s). 41 | 42 | "Modified Version" refers to any derivative made by adding to, deleting, 43 | or substituting -- in part or in whole -- any of the components of the 44 | Original Version, by changing formats or by porting the Font Software to a 45 | new environment. 46 | 47 | "Author" refers to any designer, engineer, programmer, technical 48 | writer or other person who contributed to the Font Software. 49 | 50 | PERMISSION & CONDITIONS 51 | Permission is hereby granted, free of charge, to any person obtaining 52 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 53 | redistribute, and sell modified and unmodified copies of the Font 54 | Software, subject to the following conditions: 55 | 56 | 1) Neither the Font Software nor any of its individual components, 57 | in Original or Modified Versions, may be sold by itself. 58 | 59 | 2) Original or Modified Versions of the Font Software may be bundled, 60 | redistributed and/or sold with any software, provided that each copy 61 | contains the above copyright notice and this license. These can be 62 | included either as stand-alone text files, human-readable headers or 63 | in the appropriate machine-readable metadata fields within text or 64 | binary files as long as those fields can be easily viewed by the user. 65 | 66 | 3) No Modified Version of the Font Software may use the Reserved Font 67 | Name(s) unless explicit written permission is granted by the corresponding 68 | Copyright Holder. This restriction only applies to the primary font name as 69 | presented to the users. 70 | 71 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 72 | Software shall not be used to promote, endorse or advertise any 73 | Modified Version, except to acknowledge the contribution(s) of the 74 | Copyright Holder(s) and the Author(s) or with their explicit written 75 | permission. 76 | 77 | 5) The Font Software, modified or unmodified, in part or in whole, 78 | must be distributed entirely under this license, and must not be 79 | distributed under any other license. The requirement for fonts to 80 | remain under this license does not apply to any document created 81 | using the Font Software. 82 | 83 | TERMINATION 84 | This license becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 91 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 96 | OTHER DEALINGS IN THE FONT SOFTWARE. 97 | -------------------------------------------------------------------------------- /assets/Cochineal-Roman.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevy-procedural/modelling/72fd86a68d2f11582c214742bd3326743889fdf5/assets/Cochineal-Roman.otf -------------------------------------------------------------------------------- /benches/profile_triangulation.rs: -------------------------------------------------------------------------------- 1 | //! This sub-module is used to profile the performance of selected procedural modelling algorithms. 2 | //! 3 | //! When running this on WSL2, use something like `PERF=/usr/lib/linux-tools/5.15.0-126-generic/perf cargo flamegraph --bench profile_triangulation --profile profiling` 4 | //! (see https://stackoverflow.com/a/65276025/6144727) 5 | 6 | #[cfg(test)] 7 | mod tests { 8 | use procedural_modelling::{extensions::nalgebra::*, prelude::*}; 9 | 10 | #[test] 11 | fn profile_triangulation() { 12 | let mesh = Mesh3d64::regular_polygon(1.0, 10000); 13 | /*let mesh: Mesh3d64 = Mesh2d64Curved::polygon( 14 | generate_zigzag::>(100).map(|v| VertexPayloadPNU::from_pos(v)), 15 | ).to_nd(0.01);*/ 16 | for _ in 0..1000 { 17 | let mut meta = Default::default(); 18 | let (_vs, _is) = mesh.triangulate(TriangulationAlgorithm::Sweep, &mut meta); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /benches/triangulation.rs: -------------------------------------------------------------------------------- 1 | //! A benchmark to test the speed of the triangulation 2 | 3 | // TODO: Include the fps bench as custom measurement: https://github.com/bheisler/criterion.rs/blob/master/book/src/user_guide/custom_measurements.md 4 | // TODO: Profiling https://github.com/bheisler/criterion.rs/blob/master/book/src/user_guide/profiling.md 5 | 6 | //mod bevy_bench; 7 | 8 | use bevy::{ 9 | math::{Vec2, Vec3}, 10 | utils::tracing::instrument::WithSubscriber, 11 | }; 12 | use criterion::{ 13 | criterion_group, criterion_main, AxisScale, BenchmarkId, Criterion, PlotConfiguration, 14 | Throughput, 15 | }; 16 | use procedural_modelling::{extensions::bevy::*, prelude::*}; 17 | use std::time::Duration; 18 | 19 | /* 20 | fn _make_spiral() -> BevyMesh3d { 21 | let mut mesh = BevyMesh3d::regular_star(1.0, 0.8, 30); 22 | mesh.transform( 23 | &Transform::from_translation(Vec3::new(0.0, -0.99, 0.0)) 24 | .with_rotation(Quat::from_rotation_z(PI)), 25 | ); 26 | let trans = Transform::from_rotation(Quat::from_rotation_y(0.3)) 27 | .with_translation(Vec3::new(0.4, 0.3, 0.0)); 28 | let mut f = mesh.extrude_ex(mesh.edge_between(1, 0).unwrap().id(), trans, true, true); 29 | for _ in 0..5 { 30 | f = mesh.extrude_face_ex(f, trans, true, true); 31 | } 32 | mesh 33 | }*/ 34 | 35 | fn zigzag(n: usize) -> BevyMesh3d { 36 | BevyMesh3d::polygon( 37 | generate_zigzag::(n).map(|v| BevyVertexPayload3d::from_pos(Vec3::new(v.x, v.y, 0.0))), 38 | ) 39 | } 40 | 41 | fn bench_triangulation(c: &mut Criterion) { 42 | for (mesh_name, difficulty, is_convex, maker) in [ 43 | ( 44 | "Circle", 45 | 1, 46 | true, 47 | Box::new(|n| BevyMesh3d::regular_star(1.0, 1.0, n)) as Box BevyMesh3d>, 48 | ), 49 | /*( 50 | "Zigzag", 51 | 1, 52 | false, 53 | Box::new(|n| zigzag(n)) as Box BevyMesh3d>, 54 | ),*/ 55 | //("Star", BevyMesh3d::regular_star(2.0, 0.9, 1000)), 56 | //("Spiral", _make_spiral()), 57 | ] { 58 | let mut group = c.benchmark_group(format!("Triangulation {}", mesh_name)); 59 | // allow the lowest minimum number of samples since the large meshes are really slow. 60 | group 61 | .sample_size(10) 62 | .measurement_time(Duration::from_secs(5)); 63 | 64 | let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); 65 | group.plot_config(plot_config); 66 | 67 | for size in [10, 100, 1000, 10_000,/* 100_000, 1_000_000*/] { 68 | let mesh = maker(size); 69 | 70 | let mut create_bench = 71 | |algo_name: &str, max_size: usize, algo: TriangulationAlgorithm| { 72 | if (size * difficulty) > max_size { 73 | return; 74 | } 75 | group.throughput(Throughput::Elements(size as u64)); 76 | group.bench_with_input( 77 | BenchmarkId::new(algo_name.to_string(), size), 78 | &mesh, 79 | |b, para: &BevyMesh3d| { 80 | b.iter(|| { 81 | let mut meta = Default::default(); 82 | para.triangulate(algo, &mut meta); 83 | }) 84 | }, 85 | ); 86 | }; 87 | 88 | create_bench("Sweep", 1000_000, TriangulationAlgorithm::Sweep); 89 | //create_bench("SweepD", 2000, TriangulationAlgorithm::SweepDynamic); 90 | create_bench("Delaunay", 1000_000, TriangulationAlgorithm::Delaunay); 91 | /*create_bench("Ears", 10_000, TriangulationAlgorithm::EarClipping); 92 | create_bench("Ears", 10_000, TriangulationAlgorithm::MinWeight); 93 | if is_convex { 94 | create_bench("Fan", 1000_000, TriangulationAlgorithm::Fan); 95 | }*/ 96 | } 97 | group.finish(); 98 | } 99 | } 100 | 101 | criterion_group!(benches, bench_triangulation); 102 | criterion_main!(benches); 103 | -------------------------------------------------------------------------------- /doc/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevy-procedural/modelling/72fd86a68d2f11582c214742bd3326743889fdf5/doc/images/demo.png -------------------------------------------------------------------------------- /doc/start.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This is a simple example of how to use the crate. 4 | -------------------------------------------------------------------------------- /playground/bevy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "playground_bevy" 3 | description = "An Bevy-based editor for the procedural_modelling crate" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | publish = false 7 | 8 | [dependencies] 9 | bevy = { version = "^0.15.0", default-features = true } 10 | bevy-inspector-egui = "^0.28.0" 11 | bevy_panorbit_camera = { version = "^0.21.1", features = ["bevy_egui"] } 12 | 13 | [dependencies.procedural_modelling] 14 | path = "../../" 15 | features = ["bevy", "meshopt", "bevy_dynamic", "svg", "fonts", "gizmo"] 16 | -------------------------------------------------------------------------------- /playground/fps_bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fps_bench" 3 | description = "A bevy-based fps benchmark" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | publish = false 7 | 8 | [dependencies] 9 | bevy = { version = "^0.15.0", default-features = true } 10 | 11 | [dependencies.procedural_modelling] 12 | path = "../../" 13 | features = ["bevy"] 14 | -------------------------------------------------------------------------------- /playground/fps_bench/FPSBench/Project.toml: -------------------------------------------------------------------------------- 1 | name = "FPSBench" 2 | uuid = "e87d3ac5-ed00-411c-b90f-26756937cac3" 3 | authors = ["Eric Skaliks"] 4 | version = "0.1.0" 5 | 6 | [deps] 7 | Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" 8 | 9 | [compat] 10 | Makie = "0.21.17" 11 | -------------------------------------------------------------------------------- /playground/fps_bench/FPSBench/src/FPSBench.jl: -------------------------------------------------------------------------------- 1 | module FPSBench 2 | 3 | greet() = print("Hello World!") 4 | 5 | end # module FPSBench 6 | -------------------------------------------------------------------------------- /playground/fps_bench/FPSBench/src/main.jl: -------------------------------------------------------------------------------- 1 | using CairoMakie 2 | using Statistics 3 | 4 | include("../../../../bench_results.jl") 5 | 6 | begin 7 | categories = Vector{Int64}() 8 | my_values = Vector{Float64}() 9 | dodge = Vector{Int64}() 10 | algo2categories = Dict{String, Int64}() 11 | mesh2categories = Dict{String, Int64}() 12 | for data_set in data 13 | for value in data_set[2] 14 | sp = split(data_set[1], "_") 15 | algo = sp[3] 16 | mesh = sp[1] * " " * sp[2] 17 | if !haskey(algo2categories, algo) 18 | algo2categories[algo] = length(algo2categories) + 1 19 | end 20 | if !haskey(mesh2categories, mesh) 21 | mesh2categories[mesh] = length(mesh2categories) + 1 22 | end 23 | push!(categories, mesh2categories[mesh]) 24 | push!(my_values, 1.0 / value) 25 | push!(dodge, algo2categories[algo]) 26 | end 27 | end 28 | 29 | # make sure that empty measurement combinations have one entry 30 | #=for algo in keys(algo2categories) 31 | for mesh in keys(mesh2categories) 32 | found = false 33 | for data_set in data 34 | sp = split(data_set[1], "_") 35 | if sp[3] == algo && sp[2] * "_" * sp[1] == mesh 36 | @show sp 37 | found = true 38 | break 39 | end 40 | end 41 | if !found 42 | println("Adding empty entry for $algo and $mesh") 43 | push!(categories, mesh2categories[mesh]) 44 | push!(my_values, 0.0) 45 | push!(dodge, algo2categories[algo]) 46 | end 47 | end 48 | end=# 49 | 50 | colors = Makie.wong_colors() 51 | 52 | # make a boxplot using Makie and save it as svg 53 | fig = Figure(resolution = (1200, 600)) 54 | ax = Axis(fig[1, 1], yscale = log2, ylabel = "FPS", 55 | xticks = (collect(values(mesh2categories)), collect(keys(mesh2categories)))) 56 | boxplot!(ax, categories, my_values, dodge = dodge, show_notch = true, color = colors[dodge]) 57 | 58 | Legend(fig[1, 2], [PolyElement(polycolor = colors[i]) for i in 1:length(keys(algo2categories))], collect(keys(algo2categories)), "Algorithms") 59 | 60 | save("./graph.png", fig) 61 | save("./assets/fps_boxplot.svg", fig) 62 | end 63 | 64 | begin 65 | 66 | 67 | categories = Vector{Int64}() 68 | my_values = Vector{Tuple{Float64, Float64}}() 69 | dodge = Vector{Int64}() 70 | algo2categories = Dict{String, Int64}() 71 | mesh2categories = Dict{String, Int64}() 72 | categories2mesh = Dict{Int64, String}() 73 | for data_set in data 74 | sp = split(data_set[1], "_") 75 | algo = sp[3] 76 | mesh = sp[1] * " " * sp[2] 77 | if !haskey(algo2categories, algo) 78 | algo2categories[algo] = length(algo2categories) + 1 79 | end 80 | if !haskey(mesh2categories, mesh) 81 | mesh2categories[mesh] = length(mesh2categories) + 1 82 | categories2mesh[mesh2categories[mesh]] = mesh 83 | end 84 | push!(categories, mesh2categories[mesh]) 85 | 86 | f_t = median(data_set[2]) 87 | r_t = median(data_set[3]) 88 | 89 | push!(my_values, (1.0 / f_t, r_t)) 90 | push!(dodge, algo2categories[algo]) 91 | 92 | end 93 | 94 | colors = Makie.wong_colors() 95 | 96 | fig = Figure(resolution = (800, 600)) 97 | ax = Axis(fig[1, 1], yscale = log10, xscale = log2, xlabel = "FPS", ylabel = "Render time [s]") 98 | ax.xreversed = true 99 | 100 | marker_names = Dict("circle 10" => '1', "circle 100" => '2', "circle 1000" => '3', "circle 10000" => '4', "circle 100000" => '5', "zigzag 1000" => 'Z') 101 | 102 | algos = collect(keys(algo2categories)) 103 | for algo in algos 104 | local indices = findall(i -> dodge[i] == algo2categories[algo], 1:length(dodge)) 105 | lines!(ax, [my_values[i] for i in indices if startswith(categories2mesh[categories[i]], "circle")], color = colors[algo2categories[algo]], label = algo) 106 | 107 | scatter!(ax, [my_values[i] for i in indices], color = colors[algo2categories[algo]], label = algo, marker = :circle, markersize = 21) 108 | scatter!(ax, [my_values[i] for i in indices], color = :white, label = algo, marker = :circle, markersize = 20) 109 | scatter!(ax, [my_values[i] for i in indices], color = colors[algo2categories[algo]], label = algo, marker = [marker_names[categories2mesh[categories[i]]] for i in indices], markersize = 12) 110 | end 111 | 112 | grid = GridLayout(fig[1, 2], tellheight = false) 113 | 114 | Legend(grid[1, 1], [PolyElement(polycolor = colors[algo2categories[algo]]) for algo in algos], algos, "Algorithms") 115 | 116 | Legend(grid[2, 1], 117 | [ 118 | [MarkerElement(color = :black, marker = :circle, markersize = 21), MarkerElement(color = :white, marker = :circle, markersize = 20), 119 | MarkerElement(color = :black, marker = m, markersize = 12)] for m in ['1', '2', '3', '4', '5', 'Z'] 120 | ], [ 121 | "Circles 10", 122 | "Circles 100", 123 | "Circles 1000", 124 | "Circles 10000", 125 | "Circles 100000", 126 | "Zigzag 1000", 127 | ], "Meshes") 128 | 129 | 130 | save("./graph.png", fig) 131 | save("./assets/fps_vs_render.svg", fig) 132 | 133 | end 134 | 135 | -------------------------------------------------------------------------------- /playground/wgpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "playground_wgpu" 3 | description = "A wgpu-based editor for the procedural_modelling crate" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | publish = false 7 | 8 | [dependencies] 9 | wgpu = { version = "23.0.1" } 10 | winit = { version = "0.29", features = ["rwh_05"] } 11 | env_logger = "0.10" 12 | pollster = "0.2" 13 | log = "0.4" 14 | glam = "0.13" 15 | bytemuck = { version = "1.20", features = ["derive"] } 16 | 17 | [dependencies.procedural_modelling] 18 | path = "../../" 19 | features = ["wgpu", "svg", "fonts"] 20 | -------------------------------------------------------------------------------- /playground/wgpu/src/shader.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | @location(0) position: vec4, 3 | @location(1) normal: vec3, 4 | @location(2) tex_coord: vec2, 5 | }; 6 | 7 | struct VertexOutput { 8 | @builtin(position) position: vec4, 9 | @location(0) normal: vec3, 10 | @location(1) tex_coord: vec2, 11 | }; 12 | 13 | @group(0) @binding(0) var modelViewProjectionMatrix: mat4x4; 14 | 15 | @vertex 16 | fn vs_main(input: VertexInput) -> VertexOutput { 17 | var output: VertexOutput; 18 | output.position = modelViewProjectionMatrix * input.position; 19 | output.normal = input.normal; 20 | output.tex_coord = input.tex_coord; 21 | return output; 22 | } 23 | 24 | @group(0) @binding(1) var r_color: texture_2d; 25 | 26 | @fragment 27 | fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { 28 | let tex = textureLoad(r_color, vec2(vertex.tex_coord * 256.0), 0); 29 | let v = f32(tex.x) / 255.0; 30 | let base_color = vec3(1.0 - (v * 5.0), 1.0 - (v * 15.0), 1.0 - (v * 50.0)); 31 | 32 | let light_direction = normalize(vec3(0.0, -1.0, 0.0)); 33 | let light_intensity = max(dot(vertex.normal, light_direction), 0.0); 34 | let shaded_color = base_color * light_intensity; 35 | 36 | return vec4(shaded_color, 1.0); 37 | } 38 | 39 | @fragment 40 | fn fs_wire(vertex: VertexOutput) -> @location(0) vec4 { 41 | return vec4(0.0, 0.5, 0.0, 0.5); 42 | } -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_field_init_shorthand = true 2 | newline_style = "Unix" 3 | 4 | # The following lines may be uncommented on nightly Rust. 5 | # Once these features have stabilized, they should be added to the always-enabled options above. 6 | # unstable_features = true 7 | # imports_granularity = "Crate" 8 | # wrap_comments = true 9 | # comment_width = 100 10 | # normalize_comments = true -------------------------------------------------------------------------------- /src/extensions/bevy/gizmo/mod.rs: -------------------------------------------------------------------------------- 1 | //! gizmo implementations for bevy. 2 | 3 | pub mod text; 4 | mod text3d; 5 | 6 | use super::BevyMesh3d; 7 | use crate::{ 8 | math::IndexType, 9 | mesh::{EdgeBasics, Face, FaceBasics, HalfEdge, MeshBasics, VertexBasics}, 10 | tesselate::TesselationMeta, 11 | }; 12 | use bevy::prelude::*; 13 | use text::{Text3dGizmo, Text3dGizmos}; 14 | 15 | #[cfg(feature = "sweep_debug")] 16 | use crate::mesh::payload::VertexPayload; 17 | 18 | /// Show the vertex indices of a mesh in blue. 19 | pub fn show_vertex_indices(texts: &mut ResMut, mesh: &BevyMesh3d) { 20 | mesh.vertices().for_each(|v| { 21 | texts.write( 22 | Text3dGizmo::new(v.id().to_string(), v.pos().clone()) 23 | .with_color(Color::srgb(0.0, 0.0, 1.0)), 24 | ); 25 | }); 26 | } 27 | 28 | /// Visualized the tesselation meta data of a mesh. 29 | pub fn show_tesselation_meta( 30 | _texts: &mut ResMut, 31 | _mesh: &BevyMesh3d, 32 | _meta: &TesselationMeta, 33 | ) { 34 | #[cfg(feature = "sweep_debug")] 35 | for (index, t) in _meta.sweep.vertex_type.iter() { 36 | _texts.write( 37 | Text3dGizmo::new( 38 | format!("{} {:?}", index, t), 39 | *_mesh 40 | .vertices() 41 | .nth(index.index()) 42 | .unwrap() 43 | .payload() 44 | .vertex(), 45 | ) 46 | .with_color(Color::srgb(1.0, 0.0, 0.0)), 47 | ); 48 | } 49 | } 50 | 51 | /// Show the edge indices of a mesh. 52 | /// Boundary edges are red, edges with faces are green. 53 | /// Use `offset` to slightly shift them towards the face center. 54 | pub fn show_edges(texts: &mut ResMut, mesh: &BevyMesh3d, offset: f32) { 55 | mesh.edges().for_each(|e| { 56 | if let Some(f) = e.face(mesh) { 57 | let p0 = e.centroid(mesh).clone(); 58 | let p1 = f.centroid(mesh).clone(); 59 | let p01 = p0 + (p1 - p0).normalize() * offset; 60 | texts.write( 61 | Text3dGizmo::new(e.id().to_string(), p01).with_color(Color::srgb(1.0, 1.0, 0.0)), 62 | ); 63 | } else { 64 | texts.write( 65 | Text3dGizmo::new(e.id().to_string(), e.centroid(mesh).clone()) 66 | .with_color(Color::srgb(1.0, 0.0, 0.0)), 67 | ); 68 | } 69 | }); 70 | } 71 | 72 | /// Show the face indices of a mesh in green. 73 | pub fn show_faces(texts: &mut ResMut, mesh: &BevyMesh3d) { 74 | mesh.faces().for_each(|f| { 75 | texts.write( 76 | Text3dGizmo::new(f.id().to_string(), f.centroid(mesh).clone()) 77 | .with_color(Color::srgb(0.0, 1.0, 0.0)), 78 | ); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /src/extensions/bevy/gizmo/text.rs: -------------------------------------------------------------------------------- 1 | //! Implements 3d text gizmos 2 | 3 | use bevy::prelude::*; 4 | 5 | use super::text3d; 6 | pub use super::text3d::*; 7 | 8 | // TODO: Convert this to a real gizmo where the text is requested every frame and other texts are deleted. Text widgets are re-used. 9 | // TODO: move this to its own crate 10 | // TODO: Hide text when it is behind objects 11 | 12 | /// Support for 3d text gizmos 13 | pub struct Text3dGizmosPlugin; 14 | 15 | impl Plugin for Text3dGizmosPlugin { 16 | fn build(&self, app: &mut App) { 17 | app.add_plugins(text3d::Text3dPlugin) 18 | .insert_resource(Text3dGizmos { texts: vec![] }) 19 | .add_systems(Update, create_or_delete_text); 20 | } 21 | } 22 | 23 | /// A single 3d text gizmo 24 | #[derive(Clone, Debug)] 25 | pub struct Text3dGizmo { 26 | text: String, 27 | world_position: Vec3, 28 | should_remove: bool, 29 | entity: Option, 30 | color: Color, 31 | font_size: f32, 32 | } 33 | 34 | impl Text3dGizmo { 35 | /// Creates a new 3d text gizmo 36 | pub fn new(text: String, pos: Vec3) -> Self { 37 | Self { 38 | text, 39 | world_position: pos, 40 | entity: None, 41 | should_remove: false, 42 | color: Color::WHITE, 43 | font_size: 20.0, 44 | } 45 | } 46 | 47 | /// Sets the text color 48 | pub fn with_color(mut self, color: Color) -> Self { 49 | self.color = color; 50 | self 51 | } 52 | 53 | /// Sets the font size 54 | pub fn with_font_size(mut self, font_size: f32) -> Self { 55 | self.font_size = font_size; 56 | self 57 | } 58 | } 59 | 60 | /// A resource that holds all the 3d texts 61 | #[derive(Resource, Clone, Debug)] 62 | pub struct Text3dGizmos { 63 | texts: Vec, 64 | } 65 | 66 | impl Text3dGizmos { 67 | /// Add a new 3d text to the scene 68 | pub fn write(&mut self, text: Text3dGizmo) { 69 | self.texts.push(text); 70 | } 71 | 72 | fn remove_deleted(&mut self) { 73 | self.texts.retain(|text| !text.should_remove); 74 | } 75 | } 76 | 77 | fn create_or_delete_text(mut commands: Commands, mut texts: ResMut) { 78 | for text in texts.texts.iter_mut() { 79 | if let Some(entity) = text.entity { 80 | if text.should_remove { 81 | commands.entity(entity).despawn_recursive(); 82 | text.entity = None; 83 | } 84 | } else { 85 | text.entity = Some( 86 | commands 87 | .spawn((Node { 88 | position_type: PositionType::Absolute, 89 | justify_content: JustifyContent::Center, 90 | overflow: Overflow::visible(), 91 | max_width: Val::Px(0.0), 92 | ..default() 93 | },)) 94 | .with_children(|builder| { 95 | builder.spawn(( 96 | Text::new(text.text.to_string()), 97 | TextFont::from_font_size(text.font_size), 98 | TextLayout::new_with_justify(JustifyText::Center).with_no_wrap(), 99 | TextColor(text.color), 100 | text3d::Text3d::new(text.world_position, text.font_size), 101 | )); 102 | }) 103 | .id(), 104 | ); 105 | } 106 | } 107 | texts.remove_deleted(); 108 | } 109 | -------------------------------------------------------------------------------- /src/extensions/bevy/gizmo/text3d.rs: -------------------------------------------------------------------------------- 1 | //! Implements 3d text. 2 | 3 | use bevy::prelude::*; 4 | 5 | /// Support for 3d text 6 | pub struct Text3dPlugin; 7 | 8 | impl Plugin for Text3dPlugin { 9 | fn build(&self, app: &mut App) { 10 | app.add_systems(Update, update_text_positions); 11 | } 12 | } 13 | 14 | /// Component to hold text data and world position 15 | #[derive(Component, Debug)] 16 | pub struct Text3d { 17 | world_position: Vec3, 18 | font_size: f32, 19 | } 20 | 21 | impl Text3d { 22 | /// Create a new Text3d component 23 | pub fn new(world_position: Vec3, font_size: f32) -> Self { 24 | Self { 25 | world_position, 26 | font_size, 27 | } 28 | } 29 | } 30 | 31 | // System to update text entity positions based on their 3D world position 32 | fn update_text_positions( 33 | mut text_3d_query: Query<(&mut Node, &Text3d)>, 34 | mut camera: Query<(&mut Camera, &mut Transform, &GlobalTransform), With>, 35 | ) { 36 | for (mut node, text_3d) in text_3d_query.iter_mut() { 37 | let (camera, _, camera_global_transform) = camera.single_mut(); 38 | let world_position = text_3d.world_position; 39 | let Ok(viewport_position) = 40 | camera.world_to_viewport(camera_global_transform, world_position) 41 | else { 42 | continue; 43 | }; 44 | 45 | node.top = Val::Px(viewport_position.y - text_3d.font_size / 2.0); 46 | node.left = Val::Px(viewport_position.x); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/extensions/bevy/math/mat5.rs: -------------------------------------------------------------------------------- 1 | use bevy::math::Vec4; 2 | 3 | use crate::math::{Rotator, Scalar, TransformTrait, Vector4D}; 4 | 5 | #[derive(Clone, Copy, Debug, PartialEq)] 6 | pub struct Mat5 { 7 | data: [S; 25], 8 | } 9 | 10 | impl num_traits::Zero for Mat5 { 11 | fn zero() -> Self { 12 | Mat5 { 13 | data: [S::ZERO; 25], 14 | } 15 | } 16 | 17 | fn is_zero(&self) -> bool { 18 | self.data.iter().all(|&x| x.is_zero()) 19 | } 20 | } 21 | 22 | impl std::ops::Mul> for Mat5 { 23 | type Output = Mat5; 24 | 25 | fn mul(self, rhs: Mat5) -> Mat5 { 26 | let mut m: Mat5 = num_traits::Zero::zero(); 27 | for i in 0..5 { 28 | for j in 0..5 { 29 | for k in 0..5 { 30 | m.data[i * 5 + j] += self.data[i * 5 + k] * rhs.data[k * 5 + j]; 31 | } 32 | } 33 | } 34 | m 35 | } 36 | } 37 | 38 | impl std::ops::Add> for Mat5 { 39 | type Output = Mat5; 40 | 41 | fn add(self, rhs: Mat5) -> Mat5 { 42 | let mut m: Mat5 = num_traits::Zero::zero(); 43 | for i in 0..25 { 44 | m.data[i] = self.data[i] + rhs.data[i]; 45 | } 46 | m 47 | } 48 | } 49 | 50 | impl> std::ops::Mul for Mat5 { 51 | type Output = Vec4; 52 | 53 | fn mul(self, v: Vec4) -> Vec4 { 54 | let rhs = [v.x(), v.y(), v.z(), v.w(), S::ONE]; 55 | let mut res = [S::ZERO; 5]; 56 | for i in 0..5 { 57 | for j in 0..5 { 58 | res[i] += self.data[i * 5 + j] * rhs[j]; 59 | } 60 | } 61 | Vec4::new(res[0], res[1], res[2], res[3]) 62 | } 63 | } 64 | 65 | impl Mat5 { 66 | const IDENTITY: Self = Mat5 { 67 | data: [ 68 | S::ONE, 69 | S::ZERO, 70 | S::ZERO, 71 | S::ZERO, 72 | S::ZERO, // 73 | S::ZERO, 74 | S::ONE, 75 | S::ZERO, 76 | S::ZERO, 77 | S::ZERO, // 78 | S::ZERO, 79 | S::ZERO, 80 | S::ONE, 81 | S::ZERO, 82 | S::ZERO, // 83 | S::ZERO, 84 | S::ZERO, 85 | S::ZERO, 86 | S::ONE, 87 | S::ZERO, // 88 | S::ZERO, 89 | S::ZERO, 90 | S::ZERO, 91 | S::ZERO, 92 | S::ONE, // 93 | ], 94 | }; 95 | } 96 | 97 | impl Default for Mat5 { 98 | fn default() -> Self { 99 | Self::IDENTITY 100 | } 101 | } 102 | 103 | // TODO: dummy implementation 104 | #[derive(Clone, Debug, PartialEq)] 105 | pub struct Vec4Rotator {} 106 | 107 | impl Rotator for Vec4Rotator {} 108 | 109 | impl TransformTrait for Mat5 { 110 | type Vec = Vec4; 111 | type Rot = Vec4Rotator; 112 | 113 | fn identity() -> Self { 114 | Mat5::IDENTITY 115 | } 116 | 117 | fn from_rotation(_: Self::Rot) -> Self { 118 | todo!("Not implemented"); 119 | } 120 | 121 | fn from_rotation_arc(_from: Self::Vec, _to: Self::Vec) -> Self { 122 | todo!("Not implemented"); 123 | } 124 | 125 | fn from_translation(v: Self::Vec) -> Self { 126 | let mut m = Mat5::IDENTITY; 127 | m.data[4] = v.x; 128 | m.data[9] = v.y; 129 | m.data[14] = v.z; 130 | m.data[19] = v.w; 131 | m 132 | } 133 | 134 | fn from_scale(v: Self::Vec) -> Self { 135 | let mut m = Mat5::IDENTITY; 136 | m.data[0] = v.x; 137 | m.data[6] = v.y; 138 | m.data[12] = v.z; 139 | m.data[18] = v.w; 140 | m 141 | } 142 | 143 | fn with_scale(&self, v: Self::Vec) -> Self { 144 | let mut m = *self; 145 | m.data[0] *= v.x; 146 | m.data[6] *= v.y; 147 | m.data[12] *= v.z; 148 | m.data[18] *= v.w; 149 | m 150 | } 151 | 152 | fn with_translation(&self, v: Self::Vec) -> Self { 153 | let mut m = *self; 154 | m.data[4] += v.x; 155 | m.data[9] += v.y; 156 | m.data[14] += v.z; 157 | m.data[19] += v.w; 158 | m 159 | } 160 | 161 | #[inline(always)] 162 | fn apply(&self, v: Self::Vec) -> Self::Vec { 163 | *self * v 164 | } 165 | 166 | #[inline(always)] 167 | fn apply_vec(&self, v: Self::Vec) -> Self::Vec { 168 | // don't apply translation 169 | let mut res = Vec4::ZERO; 170 | for i in 0..4 { 171 | for j in 0..4 { 172 | res[i] += self.data[i * 5 + j] * v[j]; 173 | } 174 | } 175 | res 176 | } 177 | 178 | #[inline(always)] 179 | fn chain(&self, other: &Self) -> Self { 180 | *self * *other 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/extensions/bevy/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bevy/GLAM f32 implementation of the mathematical traits. 2 | 3 | mod mat5; 4 | mod polygon; 5 | mod vec2; 6 | mod vec3; 7 | mod vec4; 8 | mod quat; 9 | 10 | pub use polygon::*; 11 | -------------------------------------------------------------------------------- /src/extensions/bevy/math/polygon.rs: -------------------------------------------------------------------------------- 1 | use crate::math::Polygon; 2 | use bevy::math::Vec2; 3 | 4 | /// A polygon in 2D space. 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct Polygon2dBevy { 7 | vertices: Vec, 8 | } 9 | 10 | impl Polygon for Polygon2dBevy { 11 | fn from_points(points: &[Vec2]) -> Self { 12 | Self { 13 | vertices: points.to_vec(), 14 | } 15 | } 16 | 17 | fn points(&self) -> &[Vec2] { 18 | &self.vertices 19 | } 20 | 21 | fn num_points(&self) -> usize { 22 | self.vertices.len() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/extensions/bevy/math/quat.rs: -------------------------------------------------------------------------------- 1 | use crate::math::{Quarternion, Rotator}; 2 | use bevy::math::{Quat, Vec3, Vec4}; 3 | 4 | impl Quarternion for Quat { 5 | type S = f32; 6 | type Vec3 = Vec3; 7 | type Vec4 = Vec4; 8 | 9 | #[inline(always)] 10 | fn identity() -> Self { 11 | Quat::IDENTITY 12 | } 13 | 14 | #[inline(always)] 15 | fn from_rotation_arc(from: Vec3, to: Vec3) -> Self { 16 | //assert!((from.length() - 1.0).abs() < 0.01); 17 | //assert!((to.length() - 1.0).abs() < 0.01); 18 | Quat::from_rotation_arc(from, to) 19 | } 20 | 21 | #[inline(always)] 22 | fn from_axis_angle(axis: Self::Vec3, angle: Self::S) -> Self { 23 | Quat::from_axis_angle(axis, angle) 24 | } 25 | 26 | #[inline(always)] 27 | fn axis_angle(&self) -> (Self::Vec3, Self::S) { 28 | self.to_axis_angle() 29 | } 30 | 31 | #[inline(always)] 32 | fn vec4(&self) -> Vec4 { 33 | Vec4::new(self.x, self.y, self.z, self.w) 34 | } 35 | } 36 | 37 | impl Rotator for Quat {} 38 | -------------------------------------------------------------------------------- /src/extensions/bevy/math/vec4.rs: -------------------------------------------------------------------------------- 1 | use super::mat5::{Mat5, Vec4Rotator}; 2 | use crate::math::{HasZero, Scalar, TransformTrait, Transformable, Vector, Vector4D}; 3 | use bevy::math::Vec4; 4 | 5 | impl HasZero for Vec4 { 6 | #[inline(always)] 7 | fn zero() -> Self { 8 | Vec4::ZERO 9 | } 10 | 11 | #[inline(always)] 12 | fn is_zero(&self) -> bool { 13 | *self == Vec4::ZERO 14 | } 15 | } 16 | 17 | impl Vector for Vec4 { 18 | #[inline(always)] 19 | fn distance(&self, other: &Self) -> f32 { 20 | Vec4::distance(*self, *other) 21 | } 22 | 23 | #[inline(always)] 24 | fn distance_squared(&self, other: &Self) -> f32 { 25 | Vec4::distance_squared(*self, *other) 26 | } 27 | 28 | #[inline(always)] 29 | fn length(&self) -> f32 { 30 | Vec4::length(*self) 31 | } 32 | 33 | #[inline(always)] 34 | fn length_squared(&self) -> f32 { 35 | Vec4::length_squared(*self) 36 | } 37 | 38 | #[inline(always)] 39 | fn dot(&self, other: &Self) -> f32 { 40 | Vec4::dot(*self, *other) 41 | } 42 | 43 | #[inline(always)] 44 | fn x(&self) -> f32 { 45 | self.x 46 | } 47 | 48 | #[inline(always)] 49 | fn y(&self) -> f32 { 50 | self.y 51 | } 52 | 53 | #[inline(always)] 54 | fn z(&self) -> f32 { 55 | self.z 56 | } 57 | 58 | #[inline(always)] 59 | fn w(&self) -> f32 { 60 | self.w 61 | } 62 | 63 | #[inline(always)] 64 | fn normalize(&self) -> Self { 65 | Vec4::normalize(*self) 66 | } 67 | 68 | #[inline(always)] 69 | fn splat(value: f32) -> Self { 70 | Vec4::splat(value) 71 | } 72 | 73 | #[inline(always)] 74 | fn from_x(x: f32) -> Self { 75 | Vec4::new(x, 0.0, 0.0, 0.0) 76 | } 77 | 78 | #[inline(always)] 79 | fn from_xy(x: f32, y: f32) -> Self { 80 | Vec4::new(x, y, 0.0, 0.0) 81 | } 82 | 83 | #[inline(always)] 84 | fn from_xyz(x: f32, y: f32, z: f32) -> Self { 85 | Vec4::new(x, y, z, 0.0) 86 | } 87 | 88 | #[inline(always)] 89 | fn is_about(&self, other: &Self, epsilon: f32) -> bool { 90 | self.x.is_about(other.x, epsilon) 91 | && self.y.is_about(other.y, epsilon) 92 | && self.z.is_about(other.z, epsilon) 93 | && self.w.is_about(other.w, epsilon) 94 | } 95 | } 96 | 97 | impl Vector4D for Vec4 { 98 | type S = f32; 99 | 100 | #[inline(always)] 101 | fn new(x: f32, y: f32, z: f32, w: f32) -> Self { 102 | Vec4::new(x, y, z, w) 103 | } 104 | } 105 | 106 | // TODO: implement more methods to improve performance 107 | impl Transformable<4> for Vec4 { 108 | type Rot = Vec4Rotator; 109 | type S = f32; 110 | type Trans = Mat5; 111 | type Vec = Vec4; 112 | 113 | fn transform(&mut self, t: &Self::Trans) -> &mut Self { 114 | *self = t.apply(*self); 115 | self 116 | } 117 | 118 | fn lerp(&mut self, other: &Self, t: Self::S) -> &mut Self { 119 | *self = Vec4::lerp(*self, *other, t); 120 | self 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/extensions/bevy/mesh2d.rs: -------------------------------------------------------------------------------- 1 | use super::{Polygon2dBevy, BevyMesh3d, BevyVertexPayload2d, BevyVertexPayload3d}; 2 | use crate::{ 3 | halfedge::{ 4 | HalfEdgeFaceImpl, HalfEdgeImpl, HalfEdgeImplMeshType, HalfEdgeMeshImpl, HalfEdgeVertexImpl, 5 | }, 6 | math::HasPosition, 7 | mesh::{ 8 | CurvedEdge, CurvedEdgePayload, CurvedEdgeType, EdgeBasics, EmptyEdgePayload, 9 | EmptyFacePayload, EmptyMeshPayload, EuclideanMeshType, MeshBasics, MeshType, 10 | MeshTypeHalfEdge, 11 | }, 12 | }; 13 | use bevy::math::{Affine2, Vec2, Vec3}; 14 | 15 | /// A mesh type for bevy with 16 | /// - 2D vertices, 17 | /// - 32 bit indices, 18 | /// - no face payloads, 19 | /// - f32 vertex positions and uv coordinates, 20 | /// - but no vertex normals 21 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] 22 | pub struct BevyMeshType2d32; 23 | 24 | impl MeshType for BevyMeshType2d32 { 25 | type E = u32; 26 | type V = u32; 27 | type F = u32; 28 | type EP = CurvedEdgePayload<2, Self>; 29 | type VP = BevyVertexPayload2d; 30 | type FP = EmptyFacePayload; 31 | type MP = EmptyMeshPayload; 32 | type Mesh = BevyMesh2d; 33 | type Face = HalfEdgeFaceImpl; 34 | type Edge = HalfEdgeImpl; 35 | type Vertex = HalfEdgeVertexImpl; 36 | } 37 | 38 | impl EuclideanMeshType<2> for BevyMeshType2d32 { 39 | type S = f32; 40 | type Vec = Vec2; 41 | type Vec2 = Vec2; 42 | type Trans = Affine2; 43 | type Rot = f32; 44 | type Poly = Polygon2dBevy; 45 | } 46 | 47 | impl HalfEdgeImplMeshType for BevyMeshType2d32 {} 48 | impl MeshTypeHalfEdge for BevyMeshType2d32 {} 49 | 50 | impl CurvedEdge<2, BevyMeshType2d32> for HalfEdgeImpl { 51 | fn curve_type(&self) -> CurvedEdgeType<2, BevyMeshType2d32> { 52 | self.payload().curve_type() 53 | } 54 | 55 | fn set_curve_type(&mut self, curve_type: CurvedEdgeType<2, BevyMeshType2d32>) { 56 | self.payload_mut().set_curve_type(curve_type); 57 | } 58 | } 59 | 60 | /// A mesh with bevy 2D vertices. Edges may be curved. 61 | pub type BevyMesh2d = HalfEdgeMeshImpl; 62 | 63 | impl HalfEdgeMeshImpl { 64 | /// Convert a BevyMesh2d to a 3d mesh. 65 | /// If there are curved edges they will be converted with the given tolerance. 66 | pub fn to_3d(&self, tol: f32) -> BevyMesh3d { 67 | BevyMesh3d::import_mesh::<_, _, _, _, BevyMeshType2d32>( 68 | self.clone().flatten_curved_edges(tol), 69 | |vp: &BevyVertexPayload2d| { 70 | BevyVertexPayload3d::from_pos(Vec3::new(vp.pos().x, vp.pos().y, 0.0)) 71 | }, 72 | |_ep| { 73 | // TODO: flatten_curved_edges seems to miss some edges? 74 | //assert!(ep.is_empty()); // no curves or anything 75 | EmptyEdgePayload::default() 76 | }, 77 | |_fp| EmptyFacePayload::default(), 78 | |_mp| EmptyMeshPayload::default(), 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/extensions/bevy/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the bevy-specific implementations 2 | 3 | mod math; 4 | mod mesh2d; 5 | mod mesh3d; 6 | mod vertex_payload_2d; 7 | mod vertex_payload_3d; 8 | 9 | pub use math::*; 10 | pub use mesh2d::*; 11 | pub use mesh3d::*; 12 | pub use vertex_payload_2d::*; 13 | pub use vertex_payload_3d::*; 14 | 15 | #[cfg(feature = "gizmo")] 16 | mod gizmo; 17 | 18 | #[cfg(feature = "gizmo")] 19 | pub use gizmo::*; -------------------------------------------------------------------------------- /src/extensions/bevy/vertex_payload_2d.rs: -------------------------------------------------------------------------------- 1 | use bevy::math::Vec2; 2 | 3 | use crate::{ 4 | math::{HasPosition, TransformTrait, Transformable}, 5 | mesh::VertexPayload, 6 | }; 7 | 8 | /// Vertex Payload for Bevy with 2d position, and uv. 9 | #[derive(Clone, PartialEq, Default, Copy)] 10 | pub struct BevyVertexPayload2d { 11 | /// The position of the vertex. 12 | position: Vec2, 13 | 14 | /// The uv coordinates of the vertex. 15 | uv: Vec2, 16 | } 17 | 18 | impl VertexPayload for BevyVertexPayload2d { 19 | fn allocate() -> Self { 20 | Self { 21 | position: Vec2::ZERO, 22 | // TODO: Zero doesn't indicate invalid uv coordinates. 23 | uv: Vec2::ZERO, 24 | } 25 | } 26 | } 27 | 28 | impl Transformable<2> for BevyVertexPayload2d { 29 | type S = f32; 30 | type Vec = Vec2; 31 | type Trans = bevy::math::Affine2; 32 | type Rot = f32; 33 | 34 | #[inline(always)] 35 | fn translate(&mut self, v: &Self::Vec) -> &mut Self { 36 | self.position += *v; 37 | // TODO: should the uv be translated as well? 38 | self 39 | } 40 | 41 | #[inline(always)] 42 | fn transform(&mut self, t: &Self::Trans) -> &mut Self { 43 | self.position = t.apply(self.position); 44 | // TODO: should the uv be transformed as well? 45 | self 46 | } 47 | 48 | #[inline(always)] 49 | fn rotate(&mut self, _r: &Self::Rot) -> &mut Self { 50 | todo!("rotate") 51 | } 52 | 53 | #[inline(always)] 54 | fn scale(&mut self, s: &Self::Vec) -> &mut Self { 55 | self.position *= *s; 56 | self 57 | } 58 | 59 | #[inline(always)] 60 | fn lerp(&mut self, other: &Self, t: Self::S) -> &mut Self { 61 | self.position = self.position.lerp(other.position, t); 62 | self.uv = self.uv.lerp(other.uv, t); 63 | self 64 | } 65 | } 66 | 67 | impl HasPosition<2, Vec2> for BevyVertexPayload2d { 68 | type S = f32; 69 | 70 | #[inline(always)] 71 | fn from_pos(v: Vec2) -> Self { 72 | Self { 73 | position: v, 74 | uv: Vec2::ZERO, 75 | } 76 | } 77 | 78 | #[inline(always)] 79 | fn pos(&self) -> &Vec2 { 80 | &self.position 81 | } 82 | 83 | #[inline(always)] 84 | fn set_pos(&mut self, v: Vec2) { 85 | self.position = v; 86 | } 87 | } 88 | 89 | impl std::fmt::Debug for BevyVertexPayload2d { 90 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 91 | write!(f, "{:+05.3}, {:+05.3}", self.position.x, self.position.y,) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/extensions/bevy/vertex_payload_3d.rs: -------------------------------------------------------------------------------- 1 | use bevy::math::{Quat, Vec2, Vec3}; 2 | 3 | use crate::{ 4 | math::{HasNormal, HasPosition, HasUV, TransformTrait, Transformable}, 5 | mesh::VertexPayload, 6 | }; 7 | 8 | /// Vertex Payload for Bevy with 3d position, normal, and uv. 9 | #[derive(Clone, PartialEq, Default, Copy)] 10 | pub struct BevyVertexPayload3d { 11 | /// The position of the vertex. 12 | position: Vec3, 13 | 14 | /// The normal of the vertex. 15 | normal: Vec3, 16 | 17 | /// The uv coordinates of the vertex. 18 | uv: Vec2, 19 | } 20 | 21 | impl VertexPayload for BevyVertexPayload3d { 22 | fn allocate() -> Self { 23 | Self { 24 | position: Vec3::ZERO, 25 | normal: Vec3::ZERO, 26 | // TODO: Zero doesn't indicate invalid uv coordinates. 27 | uv: Vec2::ZERO, 28 | } 29 | } 30 | } 31 | 32 | impl Transformable<3> for BevyVertexPayload3d { 33 | type S = f32; 34 | type Vec = Vec3; 35 | type Trans = bevy::transform::components::Transform; 36 | type Rot = Quat; 37 | 38 | #[inline(always)] 39 | fn translate(&mut self, v: &Self::Vec) -> &mut Self { 40 | self.position += *v; 41 | // TODO: should the uv be translated as well? 42 | self 43 | } 44 | 45 | #[inline(always)] 46 | fn transform(&mut self, t: &Self::Trans) -> &mut Self { 47 | self.position = t.apply(self.position); 48 | self.normal = t.apply_vec(self.normal); 49 | // TODO: should the uv be transformed as well? 50 | self 51 | } 52 | 53 | #[inline(always)] 54 | fn rotate(&mut self, r: &Self::Rot) -> &mut Self { 55 | self.position = r.mul_vec3(self.position); 56 | self.normal = r.mul_vec3(self.normal); 57 | // TODO: should the uv be transformed as well? 58 | self 59 | } 60 | 61 | #[inline(always)] 62 | fn scale(&mut self, s: &Self::Vec) -> &mut Self { 63 | self.position *= *s; 64 | self 65 | } 66 | 67 | #[inline(always)] 68 | fn lerp(&mut self, other: &Self, t: Self::S) -> &mut Self { 69 | self.position = self.position.lerp(other.position, t); 70 | // TODO: or reset to zero? 71 | self.normal = self.normal.lerp(other.normal, t); 72 | self.uv = self.uv.lerp(other.uv, t); 73 | self 74 | } 75 | } 76 | 77 | impl HasPosition<3, Vec3> for BevyVertexPayload3d { 78 | type S = f32; 79 | 80 | #[inline(always)] 81 | fn from_pos(v: Vec3) -> Self { 82 | Self { 83 | position: v, 84 | normal: Vec3::ZERO, 85 | uv: Vec2::ZERO, 86 | } 87 | } 88 | 89 | #[inline(always)] 90 | fn pos(&self) -> &Vec3 { 91 | &self.position 92 | } 93 | 94 | #[inline(always)] 95 | fn set_pos(&mut self, v: Vec3) { 96 | self.position = v; 97 | } 98 | } 99 | 100 | impl HasNormal<3, Vec3> for BevyVertexPayload3d { 101 | type S = f32; 102 | 103 | #[inline(always)] 104 | fn normal(&self) -> &Vec3 { 105 | &self.normal 106 | } 107 | 108 | #[inline(always)] 109 | fn set_normal(&mut self, normal: Vec3) { 110 | self.normal = normal; 111 | } 112 | } 113 | 114 | impl HasUV for BevyVertexPayload3d { 115 | type S = f32; 116 | 117 | #[inline(always)] 118 | fn uv(&self) -> &Vec2 { 119 | &self.uv 120 | } 121 | 122 | #[inline(always)] 123 | fn set_uv(&mut self, uv: Vec2) { 124 | self.uv = uv; 125 | } 126 | } 127 | 128 | impl std::fmt::Debug for BevyVertexPayload3d { 129 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 130 | write!( 131 | f, 132 | "{:+05.3}, {:+05.3}, {:+05.3}", 133 | self.position.x, self.position.y, self.position.z, 134 | ) 135 | } 136 | } 137 | 138 | #[cfg(feature = "nalgebra")] 139 | impl From<&crate::extensions::nalgebra::VertexPayloadPNU> 140 | for BevyVertexPayload3d 141 | { 142 | fn from(value: &crate::extensions::nalgebra::VertexPayloadPNU) -> Self { 143 | Self { 144 | position: Vec3::new( 145 | value.pos().x.to_f64() as f32, 146 | value.pos().y.to_f64() as f32, 147 | value.pos().z.to_f64() as f32, 148 | ), 149 | normal: Vec3::new( 150 | value.normal().x.to_f64() as f32, 151 | value.normal().y.to_f64() as f32, 152 | value.normal().z.to_f64() as f32, 153 | ), 154 | uv: Vec2::new(value.uv().x.to_f64() as f32, value.uv().y.to_f64() as f32), 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the backend-specific implementations 2 | 3 | #[cfg(feature = "bevy")] 4 | pub mod bevy; 5 | 6 | #[cfg(feature = "wgpu")] 7 | pub mod wgpu; 8 | 9 | #[cfg(feature = "svg")] 10 | pub mod svg; 11 | 12 | #[cfg(feature = "nalgebra")] 13 | pub mod nalgebra; 14 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! Generic nalgebra implementation of the mathematical traits. 2 | 3 | mod polygon; 4 | mod rotate; 5 | mod transform_n; 6 | mod vec2; 7 | mod vec3; 8 | mod vec4; 9 | mod vec_n; 10 | 11 | pub use polygon::*; 12 | pub use rotate::*; 13 | pub use transform_n::*; 14 | pub use vec2::*; 15 | pub use vec3::*; 16 | pub use vec4::*; 17 | pub use vec_n::*; 18 | 19 | use crate::math::Scalar; 20 | use nalgebra::{RealField, Scalar as ScalarNalgebra, SimdComplexField, SimdRealField}; 21 | 22 | // TODO: this is a bit restrictive... Can we somehow avoid using the Simd-traits? 23 | 24 | /// A scalar that can be used with nalgebra. 25 | pub trait ScalarPlus: 26 | Scalar + ScalarNalgebra + SimdComplexField + SimdRealField + RealField 27 | { 28 | } 29 | 30 | impl ScalarPlus for f32 {} 31 | impl ScalarPlus for f64 {} 32 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/math/polygon.rs: -------------------------------------------------------------------------------- 1 | use super::Vec2; 2 | use crate::math::{Polygon, Scalar}; 3 | use nalgebra::SVector; 4 | 5 | /// A polygon in 2D space. 6 | #[derive(Clone, Debug, PartialEq)] 7 | pub struct Polygon2d { 8 | vertices: Vec>, 9 | } 10 | 11 | impl Polygon> for Polygon2d { 12 | fn from_points(points: &[Vec2]) -> Self { 13 | Self { 14 | vertices: points.to_vec(), 15 | } 16 | } 17 | 18 | fn points(&self) -> &[Vec2] { 19 | &self.vertices 20 | } 21 | 22 | fn num_points(&self) -> usize { 23 | self.vertices.len() 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | use crate::math::Polygon; 31 | 32 | #[test] 33 | fn test_polygon2d() { 34 | for points in [ 35 | vec![ 36 | Vec2::new(0.0, 0.0), 37 | Vec2::new(1.0, 0.0), 38 | Vec2::new(1.0, 1.0), 39 | Vec2::new(0.0, 1.0), 40 | ], 41 | vec![], 42 | vec![Vec2::new(0.0, 0.0)], 43 | ] { 44 | let polygon = Polygon2d::from_points(&points); 45 | assert_eq!(polygon.num_points(), points.len()); 46 | assert_eq!(polygon.points(), points.as_slice()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/math/vec2.rs: -------------------------------------------------------------------------------- 1 | use super::VecN; 2 | use crate::math::{Scalar, Vector, Vector2D}; 3 | 4 | /// A 2D vector. 5 | pub type Vec2 = VecN; 6 | 7 | impl Vector2D for Vec2 { 8 | type S = S; 9 | 10 | #[inline(always)] 11 | fn new(x: S, y: S) -> Self { 12 | Self::from([x, y]) 13 | } 14 | 15 | fn perp_dot(&self, other: &Self) -> S { 16 | (self.x() * other.y()) - (self.y() * other.x()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/math/vec3.rs: -------------------------------------------------------------------------------- 1 | use super::VecN; 2 | use crate::math::{Scalar, Spherical3d, Vector3D}; 3 | 4 | /// A 3D vector. 5 | pub type Vec3 = VecN; 6 | 7 | impl Vector3D for Vec3 { 8 | type S = T; 9 | type Spherical = Vec3; 10 | 11 | fn new(x: Self::S, y: Self::S, z: Self::S) -> Self { 12 | Self::from([x, y, z]) 13 | } 14 | 15 | fn cross(&self, other: &Self) -> Self { 16 | nalgebra::Matrix::cross(self, other) 17 | } 18 | } 19 | 20 | impl Spherical3d for Vec3 { 21 | type S = T; 22 | type Vec3 = Vec3; 23 | } 24 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/math/vec4.rs: -------------------------------------------------------------------------------- 1 | use super::VecN; 2 | use crate::math::{Scalar, Vector4D}; 3 | 4 | /// A 4D vector. 5 | pub type Vec4 = VecN; 6 | 7 | impl Vector4D for Vec4 { 8 | type S = T; 9 | 10 | fn new(x: Self::S, y: Self::S, z: Self::S, w: Self::S) -> Self { 11 | Self::from([x, y, z, w]) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/math/vec_n.rs: -------------------------------------------------------------------------------- 1 | use super::{rotate::NdRotate, transform_n::NdAffine, ScalarPlus}; 2 | use crate::math::{HasZero, Scalar, TransformTrait, Transformable, Vector}; 3 | use nalgebra::SVector; 4 | 5 | /// A N-dimensional vector. 6 | pub type VecN = SVector; 7 | 8 | impl HasZero for VecN { 9 | #[inline(always)] 10 | fn zero() -> Self { 11 | Self::zeros() 12 | } 13 | 14 | #[inline(always)] 15 | fn is_zero(&self) -> bool { 16 | self.iter().all(|&x| x.is_zero()) 17 | } 18 | } 19 | 20 | impl Vector for VecN { 21 | #[inline(always)] 22 | fn distance(&self, other: &Self) -> S { 23 | self.distance_squared(other).sqrt() 24 | } 25 | 26 | #[inline(always)] 27 | fn distance_squared(&self, other: &Self) -> S { 28 | Scalar::stable_sum( 29 | self.data 30 | .as_slice() 31 | .iter() 32 | .zip(other.data.as_slice().iter()) 33 | .map(|(a, b)| (*a - *b) * (*a - *b)), 34 | ) 35 | } 36 | 37 | #[inline(always)] 38 | fn length(&self) -> S { 39 | self.length_squared().sqrt() 40 | } 41 | 42 | #[inline(always)] 43 | fn length_squared(&self) -> S { 44 | Scalar::stable_sum(self.data.as_slice().iter().map(|a| *a * *a)) 45 | } 46 | 47 | #[inline(always)] 48 | fn dot(&self, other: &Self) -> S { 49 | Scalar::stable_sum( 50 | self.data 51 | .as_slice() 52 | .iter() 53 | .zip(other.data.as_slice().iter()) 54 | .map(|(a, b)| *a * *b), 55 | ) 56 | } 57 | 58 | #[inline(always)] 59 | fn x(&self) -> S { 60 | self[0] 61 | } 62 | 63 | #[inline(always)] 64 | fn y(&self) -> S { 65 | if D >= 2 { 66 | self[1] 67 | } else { 68 | S::ZERO 69 | } 70 | } 71 | 72 | #[inline(always)] 73 | fn z(&self) -> S { 74 | if D >= 3 { 75 | self[2] 76 | } else { 77 | S::ZERO 78 | } 79 | } 80 | 81 | #[inline(always)] 82 | fn w(&self) -> S { 83 | if D >= 4 { 84 | self[3] 85 | } else { 86 | S::ZERO 87 | } 88 | } 89 | 90 | #[inline(always)] 91 | fn normalize(&self) -> Self { 92 | let length = self.length(); 93 | if length.is_zero() { 94 | *self 95 | } else { 96 | *self / length 97 | } 98 | } 99 | 100 | #[inline(always)] 101 | fn splat(value: S) -> Self { 102 | Self::from([value; D]) 103 | } 104 | 105 | #[inline(always)] 106 | fn from_x(x: S) -> Self { 107 | let mut data = [S::ZERO; D]; 108 | data[0] = x; 109 | Self::from(data) 110 | } 111 | 112 | #[inline(always)] 113 | fn from_xy(x: S, y: S) -> Self { 114 | let mut data = [S::ZERO; D]; 115 | data[0] = x; 116 | if D >= 2 { 117 | data[1] = y; 118 | } 119 | Self::from(data) 120 | } 121 | #[inline(always)] 122 | fn from_xyz(x: S, y: S, z: S) -> Self { 123 | let mut data = [S::ZERO; D]; 124 | data[0] = x; 125 | if D >= 2 { 126 | data[1] = y; 127 | } 128 | if D >= 3 { 129 | data[2] = z; 130 | } 131 | Self::from(data) 132 | } 133 | 134 | #[inline(always)] 135 | fn is_about(&self, other: &Self, eps: S) -> bool { 136 | self.iter() 137 | .zip(other.iter()) 138 | .all(|(a, b)| a.is_about(*b, eps)) 139 | } 140 | } 141 | 142 | impl Transformable for VecN { 143 | type S = S; 144 | type Rot = NdRotate; 145 | type Trans = NdAffine; 146 | type Vec = VecN; 147 | 148 | fn transform(&mut self, t: &Self::Trans) -> &mut Self { 149 | *self = t.apply(*self); 150 | self 151 | } 152 | 153 | fn translate(&mut self, v: &Self::Vec) -> &mut Self { 154 | *self += *v; 155 | self 156 | } 157 | 158 | fn scale(&mut self, s: &Self::Vec) -> &mut Self { 159 | for i in 0..D { 160 | self[i] *= s[i]; 161 | } 162 | self 163 | } 164 | 165 | fn lerp(&mut self, other: &Self, t: Self::S) -> &mut Self { 166 | for i in 0..D { 167 | self[i] = self[i].lerp(other[i], t); 168 | } 169 | self 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/mesh2d.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | halfedge::{ 3 | HalfEdgeFaceImpl, HalfEdgeImpl, HalfEdgeImplMeshType, HalfEdgeMeshImpl, HalfEdgeVertexImpl, 4 | }, 5 | math::{HasPosition, Vector}, 6 | mesh::{ 7 | CurvedEdge, CurvedEdgePayload, CurvedEdgeType, EdgeBasics, EmptyEdgePayload, 8 | EmptyFacePayload, EmptyMeshPayload, EuclideanMeshType, MeshBasics, MeshType, 9 | MeshTypeHalfEdge, 10 | }, 11 | }; 12 | 13 | use super::{MeshNd64, NdAffine, NdRotate, Polygon2d, VecN, VertexPayloadPNU}; 14 | 15 | /// A mesh type for nalgebra with 16 | /// - 2D vertices, 17 | /// - usize indices, 18 | /// - no face payloads, 19 | /// - curved edge payload, 20 | /// - f64 vertex positions and uv coordinates, 21 | /// - no vertex normals, 22 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] 23 | pub struct MeshType2d64PNUCurved; 24 | 25 | impl MeshType for MeshType2d64PNUCurved { 26 | type E = usize; 27 | type V = usize; 28 | type F = usize; 29 | type EP = CurvedEdgePayload<2, Self>; 30 | type VP = VertexPayloadPNU; 31 | type FP = EmptyFacePayload; 32 | type MP = EmptyMeshPayload; 33 | type Mesh = Mesh2d64Curved; 34 | type Face = HalfEdgeFaceImpl; 35 | type Edge = HalfEdgeImpl; 36 | type Vertex = HalfEdgeVertexImpl; 37 | } 38 | 39 | impl EuclideanMeshType<2> for MeshType2d64PNUCurved { 40 | type S = f64; 41 | type Vec = VecN; 42 | type Vec2 = VecN; 43 | type Trans = NdAffine; 44 | type Rot = NdRotate; 45 | type Poly = Polygon2d; 46 | } 47 | 48 | impl HalfEdgeImplMeshType for MeshType2d64PNUCurved {} 49 | impl MeshTypeHalfEdge for MeshType2d64PNUCurved {} 50 | 51 | impl CurvedEdge<2, MeshType2d64PNUCurved> for HalfEdgeImpl { 52 | fn curve_type(&self) -> CurvedEdgeType<2, MeshType2d64PNUCurved> { 53 | self.payload().curve_type() 54 | } 55 | 56 | fn set_curve_type(&mut self, curve_type: CurvedEdgeType<2, MeshType2d64PNUCurved>) { 57 | self.payload_mut().set_curve_type(curve_type); 58 | } 59 | } 60 | 61 | /// A mesh with 2D vertices, usize indices, f64 positions and uv coordinates, and curved edges. 62 | pub type Mesh2d64Curved = HalfEdgeMeshImpl; 63 | 64 | impl HalfEdgeMeshImpl { 65 | /// Convert a Mesh2d64Curved to a MeshNd64 mesh. 66 | /// If there are curved edges they will be converted with the given tolerance. 67 | pub fn to_nd(&self, tol: f64) -> MeshNd64 { 68 | MeshNd64::::import_mesh::<_, _, _, _, MeshType2d64PNUCurved>( 69 | self.clone().flatten_curved_edges(tol), 70 | |vp| VertexPayloadPNU::::from_pos(Vector::from_xy(vp.pos().x, vp.pos().y)), 71 | |_ep| { 72 | // TODO: flatten_curved_edges seems to miss some edges? 73 | //assert!(ep.is_empty()); // no curves or anything 74 | EmptyEdgePayload::default() 75 | }, 76 | |_fp| EmptyFacePayload::default(), 77 | |_mp| EmptyMeshPayload::default(), 78 | ) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | #[cfg(feature = "nalgebra")] 84 | mod tests { 85 | use super::*; 86 | use crate::{extensions::nalgebra::Vec3, prelude::*}; 87 | 88 | #[test] 89 | fn test_mesh2d64curved_construction() { 90 | let n = 100; 91 | let radius = 1.0; 92 | 93 | let mut mesh = Mesh2d64Curved::new(); 94 | mesh.insert_regular_star(radius, radius, n); 95 | assert_eq!(mesh.num_vertices(), n); 96 | assert_eq!(mesh.num_edges(), 2 * n); 97 | assert_eq!(mesh.num_faces(), 1); 98 | assert!(mesh.has_consecutive_vertex_ids()); 99 | assert!(mesh.is_open()); 100 | assert!(mesh.check().is_ok()); 101 | 102 | let m3d = mesh.to_nd::<3>(1.0); 103 | assert_eq!(m3d.num_vertices(), n); 104 | assert_eq!(m3d.num_edges(), 2 * n); 105 | assert_eq!(m3d.num_faces(), 1); 106 | assert!(m3d.has_consecutive_vertex_ids()); 107 | assert!(m3d.is_open()); 108 | assert!(m3d.check().is_ok()); 109 | 110 | let f = m3d.faces().next().expect("no face"); 111 | assert!(f.is_convex(&m3d)); 112 | assert!(f.is_planar2(&m3d)); 113 | //assert!(f.is_simple(&m3d)); 114 | assert_eq!(f.normal(&m3d).normalize(), Vec3::from_xyz(0.0, 0.0, 1.0)); 115 | let p = f.as_polygon(&m3d); 116 | assert!( 117 | (p.signed_area().abs() - (regular_polygon_area(radius, n))).abs() 118 | <= f64::EPSILON.sqrt() 119 | ); 120 | 121 | let m10d = mesh.to_nd::<10>(1.0); 122 | assert!(m10d.check().is_ok()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/mesh_nd.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | halfedge::{ 3 | HalfEdgeFaceImpl, HalfEdgeImpl, HalfEdgeImplMeshType, HalfEdgeMeshImpl, HalfEdgeVertexImpl, 4 | }, 5 | mesh::{ 6 | EmptyEdgePayload, EmptyFacePayload, EmptyMeshPayload, EuclideanMeshType, MeshType, 7 | MeshType3D, MeshTypeHalfEdge, 8 | }, 9 | }; 10 | 11 | use super::{NdAffine, NdRotate, Polygon2d, VecN, VertexPayloadPNU}; 12 | 13 | /// A mesh type for nalgebra with 14 | /// - nd vertices, 15 | /// - usize indices, 16 | /// - no face or edge payload, 17 | /// - f64 vertex positions, normals, and uv coordinates 18 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] 19 | pub struct MeshTypeNd64PNU; 20 | 21 | /// 2d variant of MeshTypeNd64PNU 22 | pub type MeshType2d64PNU = MeshTypeNd64PNU<2>; 23 | /// 3d variant of MeshTypeNd64PNU 24 | pub type MeshType3d64PNU = MeshTypeNd64PNU<3>; 25 | /// 4d variant of MeshTypeNd64PNU 26 | pub type MeshType4d64PNU = MeshTypeNd64PNU<4>; 27 | 28 | impl MeshType for MeshTypeNd64PNU { 29 | type E = usize; 30 | type V = usize; 31 | type F = usize; 32 | type EP = EmptyEdgePayload; 33 | type VP = VertexPayloadPNU; 34 | type FP = EmptyFacePayload; 35 | type MP = EmptyMeshPayload; 36 | type Mesh = MeshNd64; 37 | type Face = HalfEdgeFaceImpl; 38 | type Edge = HalfEdgeImpl; 39 | type Vertex = HalfEdgeVertexImpl; 40 | } 41 | impl EuclideanMeshType for MeshTypeNd64PNU { 42 | type S = f64; 43 | type Vec = VecN; 44 | type Vec2 = VecN; 45 | type Trans = NdAffine; 46 | type Rot = NdRotate; 47 | type Poly = Polygon2d; 48 | } 49 | impl HalfEdgeImplMeshType for MeshTypeNd64PNU {} 50 | impl MeshTypeHalfEdge for MeshTypeNd64PNU {} 51 | impl MeshType3D for MeshTypeNd64PNU<3> {} 52 | 53 | /// A mesh with 54 | /// - nalgebra nd vertices, 55 | /// - usize indices, 56 | /// - f64 positions, normals, and uv coordinates 57 | pub type MeshNd64 = HalfEdgeMeshImpl>; 58 | /// 2d variant of MeshNd64 59 | pub type Mesh2d64 = MeshNd64<2>; 60 | /// 3d variant of MeshNd64 61 | pub type Mesh3d64 = MeshNd64<3>; 62 | /// 4d variant of MeshNd64 63 | pub type Mesh4d64 = MeshNd64<4>; 64 | 65 | /// 64-bit 3d variant of the half-edge vertex 66 | pub type Mesh3d64Vertex = HalfEdgeVertexImpl>; 67 | 68 | /// 64-bit 3d variant of the half-edge edge 69 | pub type Mesh3d64Edge = HalfEdgeImpl>; 70 | 71 | /// 64-bit 3d variant of the half-edge face 72 | pub type Mesh3d64Face = HalfEdgeFaceImpl>; 73 | -------------------------------------------------------------------------------- /src/extensions/nalgebra/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains backend-independent nalgebra implementations 2 | 3 | mod default_vertex_payload; 4 | mod math; 5 | mod mesh2d; 6 | mod mesh_nd; 7 | 8 | pub use default_vertex_payload::*; 9 | pub use math::*; 10 | pub use mesh2d::*; 11 | pub use mesh_nd::*; 12 | -------------------------------------------------------------------------------- /src/extensions/svg/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the svg-specific implementations 2 | 3 | use crate::mesh::{ 4 | CurvedEdge, DefaultEdgePayload, DefaultFacePayload, EuclideanMeshType, MeshTypeHalfEdge, 5 | }; 6 | 7 | mod svg; 8 | 9 | /// Backend trait for SVG import/export. 10 | pub trait BackendSVG> 11 | where 12 | T::Edge: CurvedEdge<2, T>, 13 | T::FP: DefaultFacePayload, 14 | T::EP: DefaultEdgePayload, 15 | { 16 | /// Import an SVG string into the mesh. 17 | #[cfg(feature = "svg")] 18 | fn import_svg(&mut self, svg: &str) -> &mut Self 19 | where 20 | T: MeshTypeHalfEdge, 21 | { 22 | svg::import_svg::(self, svg); 23 | self 24 | } 25 | 26 | /// Create a new mesh from an SVG string. 27 | #[cfg(feature = "svg")] 28 | fn from_svg(svg: &str) -> Self 29 | where 30 | T: MeshTypeHalfEdge, 31 | { 32 | let mut mesh = Self::default(); 33 | mesh.import_svg(svg); 34 | mesh 35 | } 36 | } 37 | 38 | impl> BackendSVG for T::Mesh 39 | where 40 | T::Edge: CurvedEdge<2, T>, 41 | T::FP: DefaultFacePayload, 42 | T::EP: DefaultEdgePayload, 43 | { 44 | } 45 | -------------------------------------------------------------------------------- /src/extensions/svg/svg.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::Vector, 3 | mesh::{ 4 | CurvedEdge, DefaultEdgePayload, DefaultFacePayload, EuclideanMeshType, MeshTypeHalfEdge, 5 | PathBuilder, 6 | }, 7 | }; 8 | 9 | fn import_group + MeshTypeHalfEdge>(mesh: &mut T::Mesh, group: &usvg::Group) 10 | where 11 | T::Edge: CurvedEdge<2, T>, 12 | T::EP: DefaultEdgePayload, 13 | T::FP: DefaultFacePayload, 14 | { 15 | for c in group.children() { 16 | match c { 17 | usvg::Node::Group(g) => { 18 | import_group::(mesh, g.as_ref()); 19 | } 20 | usvg::Node::Path(p) => { 21 | import_path::(mesh, p.as_ref()); 22 | } 23 | usvg::Node::Text(t) => { 24 | println!("Text: {:#?}", t); 25 | todo!(); 26 | } 27 | usvg::Node::Image(i) => { 28 | println!("Image: {:#?}", i); 29 | todo!(); 30 | } 31 | } 32 | } 33 | } 34 | 35 | fn import_path + MeshTypeHalfEdge>(mesh: &mut T::Mesh, path: &usvg::Path) 36 | where 37 | T::Edge: CurvedEdge<2, T>, 38 | T::EP: DefaultEdgePayload, 39 | T::FP: DefaultFacePayload, 40 | { 41 | if !path.is_visible() { 42 | return; 43 | } 44 | 45 | // let fill = path.fill(); 46 | // let stroke = path.stroke(); 47 | // let po = path.paint_order(); 48 | // let transform = path.abs_transform(); 49 | 50 | let v = |p: usvg::tiny_skia_path::Point| T::Vec::from_xy(T::S::from(p.x), T::S::from(p.y)); 51 | 52 | let mut pb = PathBuilder::::new(mesh); 53 | 54 | let mut is_first = true; 55 | for s in path.data().segments() { 56 | match s { 57 | usvg::tiny_skia_path::PathSegment::MoveTo(p) => { 58 | assert!(is_first); 59 | pb.move_to_new(v(p)); 60 | //pb = PathBuilder::::start(mesh, v(p)); 61 | } 62 | usvg::tiny_skia_path::PathSegment::LineTo(p) => { 63 | let end = pb.add_vertex_autoclose(v(p)); 64 | pb.line_to(end); 65 | } 66 | usvg::tiny_skia_path::PathSegment::QuadTo(c1, p) => { 67 | let end = pb.add_vertex_autoclose(v(p)); 68 | pb.quad_to(v(c1), end); 69 | } 70 | usvg::tiny_skia_path::PathSegment::CubicTo(c1, c2, p) => { 71 | let end = pb.add_vertex_autoclose(v(p)); 72 | //pb.line_to(end); 73 | pb.cubic_to(v(c1), v(c2), end); 74 | } 75 | usvg::tiny_skia_path::PathSegment::Close => { 76 | pb.close(Default::default()); 77 | } 78 | } 79 | is_first = false; 80 | } 81 | 82 | if !pb.has_face() { 83 | pb.close(Default::default()); 84 | } 85 | } 86 | 87 | pub(crate) fn import_svg + MeshTypeHalfEdge>(mesh: &mut T::Mesh, svg: &str) 88 | where 89 | T::Edge: CurvedEdge<2, T>, 90 | T::EP: DefaultEdgePayload, 91 | T::FP: DefaultFacePayload, 92 | { 93 | let res = usvg::Tree::from_str(&svg, &usvg::Options::default()); 94 | if let Err(e) = res { 95 | match e { 96 | usvg::Error::ParsingFailed(reason) => match reason { 97 | usvg::roxmltree::Error::NoRootNode => { 98 | import_svg::( 99 | mesh, 100 | ("".to_string() + svg + "") 101 | .as_str(), 102 | ); 103 | return; 104 | } 105 | _ => { 106 | panic!("Failed to parse SVG: {:#?}", reason); 107 | } 108 | }, 109 | _ => { 110 | panic!("Failed to parse SVG: {:#?}", e); 111 | } 112 | } 113 | } 114 | let tree = res.expect("Failed to parse SVG"); 115 | import_group::(mesh, tree.root()); 116 | } 117 | -------------------------------------------------------------------------------- /src/extensions/wgpu/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the wgpu-specific implementations 2 | 3 | 4 | // TODO: move wgpu-specific implementations here -------------------------------------------------------------------------------- /src/halfedge/edge/basics.rs: -------------------------------------------------------------------------------- 1 | use super::{BackwardEdgeIterator, ForwardEdgeIterator, HalfEdgeImpl, HalfEdgeImplMeshType}; 2 | use crate::{ 3 | math::IndexType, 4 | mesh::{EdgeBasics, HalfEdge, MeshBasics}, 5 | }; 6 | 7 | impl EdgeBasics for HalfEdgeImpl { 8 | /// Returns the index of the half-edge 9 | #[inline(always)] 10 | fn id(&self) -> T::E { 11 | self.id 12 | } 13 | 14 | /// Returns the source vertex of the half-edge 15 | #[inline(always)] 16 | fn origin<'a>(&'a self, mesh: &'a T::Mesh) -> &'a T::Vertex { 17 | mesh.vertex(self.origin_id) 18 | } 19 | 20 | /// Returns the target vertex of the half-edge. Reached via the next half-edge, not the twin. 21 | #[inline(always)] 22 | fn target<'a>(&'a self, mesh: &'a T::Mesh) -> &'a T::Vertex { 23 | mesh.vertex(self.next(mesh).origin_id()) 24 | } 25 | 26 | /// Returns whether the edge (i.e., this HalfEdge or its twin) is a boundary edge 27 | #[inline(always)] 28 | fn is_boundary(&self, mesh: &T::Mesh) -> bool { 29 | self.is_boundary_self() || self.twin(mesh).is_boundary_self() 30 | } 31 | 32 | /// Returns the face payload. 33 | #[inline(always)] 34 | fn payload(&self) -> &T::EP { 35 | &self.payload 36 | } 37 | 38 | /// Returns a mutable reference to the face payload. 39 | #[inline(always)] 40 | fn payload_mut(&mut self) -> &mut T::EP { 41 | &mut self.payload 42 | } 43 | 44 | /// Iterates all half-edges incident to the same face (counter-clockwise) 45 | #[inline(always)] 46 | #[allow(refining_impl_trait)] 47 | fn edges_face<'a>(&'a self, mesh: &'a T::Mesh) -> ForwardEdgeIterator<'a, T> { 48 | ForwardEdgeIterator::new(self.clone(), mesh) 49 | } 50 | 51 | /// Iterates all half-edges incident to the same face (clockwise) 52 | #[inline(always)] 53 | #[allow(refining_impl_trait)] 54 | fn edges_face_back<'a>(&'a self, mesh: &'a T::Mesh) -> BackwardEdgeIterator<'a, T> { 55 | BackwardEdgeIterator::new(self.clone(), mesh) 56 | } 57 | 58 | #[inline(always)] 59 | fn face_ids<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator { 60 | // TODO: only works for manifold meshes 61 | let mut res = Vec::new(); 62 | let id = self.face_id(); 63 | if id != IndexType::max() { 64 | res.push(id); 65 | } 66 | let twin = self.twin(mesh); 67 | let id = twin.face_id(); 68 | if id != IndexType::max() { 69 | res.push(id); 70 | } 71 | res.into_iter() 72 | } 73 | } -------------------------------------------------------------------------------- /src/halfedge/edge/mod.rs: -------------------------------------------------------------------------------- 1 | mod basics; 2 | mod halfedge; 3 | mod iterator; 4 | 5 | pub use iterator::*; 6 | 7 | use super::HalfEdgeImplMeshType; 8 | use crate::{ 9 | math::IndexType, 10 | mesh::{DefaultEdgePayload, Edge, EdgeBasics, EdgePayload}, 11 | util::Deletable, 12 | }; 13 | 14 | // TODO: Memory alignment? 15 | // TODO: include a way to explicitly access faces around vertex/face? https://en.wikipedia.org/wiki/Polygon_mesh 16 | 17 | /// Half-edge inspired data structure 18 | #[derive(Clone)] 19 | pub struct HalfEdgeImpl { 20 | /// the index of the half-edge 21 | id: T::E, 22 | 23 | /// next half-edge incident to the same face 24 | /// (first edge encountered when traversing around the target vertex in clockwise order). 25 | /// This will always exist. If the edge is a boundary, it will wrap around the boundary. 26 | next: T::E, 27 | 28 | /// The other, opposite half-edge. 29 | /// This will always exist. 30 | twin: T::E, 31 | 32 | /// The previous half-edge incident to the same face. 33 | /// This will always exist. If the edge is a boundary, it will wrap around the boundary. 34 | prev: T::E, 35 | 36 | /// The source vertex of the half-edge. 37 | /// This will always exist. 38 | origin_id: T::V, 39 | 40 | /// The face the half-edge is incident to. 41 | /// The face lies to the left of the half-edge. 42 | /// Half-edges traverse the boundary of the face in counter-clockwise order. 43 | /// This index will be FaceIndex.max() if it doesn't exist, i.e., if the edge is a boundary. 44 | face: T::F, 45 | 46 | /// Some user-defined payload 47 | payload: T::EP, 48 | } 49 | 50 | impl Edge for HalfEdgeImpl { 51 | type T = T; 52 | } 53 | 54 | impl std::fmt::Debug for HalfEdgeImpl { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | let payload = if self.payload.is_empty() { 57 | "".to_string() 58 | } else { 59 | format!(", payload: {:?}", self.payload) 60 | }; 61 | write!( 62 | f, 63 | "{} --{}--> ; twin: {}, face: {} [{}] {} {}", 64 | self.origin_id.index(), 65 | self.id().index(), 66 | self.twin.index(), 67 | self.prev.index(), 68 | if self.face == IndexType::max() { 69 | "none".to_string() 70 | } else { 71 | self.face.index().to_string() 72 | }, 73 | self.next.index(), 74 | payload 75 | )?; 76 | if !self.payload.is_empty() { 77 | write!(f, ", payload: {:?}", self.payload)?; 78 | } 79 | Ok(()) 80 | } 81 | } 82 | 83 | impl Deletable for HalfEdgeImpl { 84 | fn delete(&mut self) { 85 | assert!(self.id != IndexType::max()); 86 | self.id = IndexType::max(); 87 | } 88 | 89 | fn is_deleted(&self) -> bool { 90 | self.id == IndexType::max() 91 | } 92 | 93 | fn set_id(&mut self, id: T::E) { 94 | assert!(self.id == IndexType::max()); 95 | assert!(id != IndexType::max()); 96 | assert!(self.next != id); 97 | assert!(self.prev != id); 98 | self.id = id; 99 | } 100 | 101 | fn allocate() -> Self { 102 | Self { 103 | id: IndexType::max(), 104 | next: IndexType::max(), 105 | twin: IndexType::max(), 106 | prev: IndexType::max(), 107 | origin_id: IndexType::max(), 108 | face: IndexType::max(), 109 | payload: T::EP::allocate(), 110 | } 111 | } 112 | } 113 | 114 | impl Default for HalfEdgeImpl 115 | where 116 | T::EP: DefaultEdgePayload, 117 | { 118 | /// Creates a deleted edge 119 | fn default() -> Self { 120 | Self { 121 | id: IndexType::max(), 122 | next: IndexType::max(), 123 | twin: IndexType::max(), 124 | prev: IndexType::max(), 125 | origin_id: IndexType::max(), 126 | face: IndexType::max(), 127 | payload: T::EP::default(), 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/halfedge/mesh/builder/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod halfedge; 3 | mod semi; 4 | mod vertex; 5 | -------------------------------------------------------------------------------- /src/halfedge/mesh/builder/path.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | halfedge::{HalfEdgeMeshImpl, HalfEdgeMeshType}, 3 | mesh::{ 4 | DefaultEdgePayload, EdgeBasics, HalfEdge, MeshBasics, MeshBuilder, MeshHalfEdgeBuilder, 5 | MeshPathBuilder, 6 | }, 7 | }; 8 | 9 | impl MeshPathBuilder for HalfEdgeMeshImpl { 10 | /// Generate a path from the finite iterator of positions and return the halfedges pointing to the first and last vertex. 11 | fn insert_path(&mut self, vp: impl IntoIterator) -> (T::E, T::E) 12 | where 13 | T::EP: DefaultEdgePayload, 14 | { 15 | // TODO: create this directly without the builder functions 16 | 17 | let mut iter = vp.into_iter(); 18 | let p0 = iter.next().expect("Path must have at least one vertex"); 19 | let p1 = iter.next().expect("Path must have at least two vertices"); 20 | let (v0, v) = self.add_isolated_edge_default(p0, p1); 21 | let first = self.shared_edge(v0, v).unwrap(); 22 | let mut input = first.id(); 23 | let mut output = first.twin_id(); 24 | for pos in iter { 25 | self.add_vertex_via_edge_default(input, output, pos); 26 | let n = self.edge(input).next(self); 27 | input = n.id(); 28 | output = n.twin_id(); 29 | } 30 | 31 | (first.twin_id(), input) 32 | } 33 | 34 | fn add_isolated_edge_default(&mut self, a: T::VP, b: T::VP) -> (T::V, T::V) 35 | where 36 | T::EP: DefaultEdgePayload, 37 | { 38 | self.add_isolated_edge(a, T::EP::default(), b, T::EP::default()) 39 | } 40 | 41 | fn insert_loop(&mut self, vp: impl IntoIterator) -> T::E 42 | where 43 | T::EP: DefaultEdgePayload, 44 | { 45 | let (first, last) = self.insert_path(vp); 46 | self.insert_edge(first, Default::default(), last, Default::default()); 47 | return first; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/halfedge/mesh/builder/subdivision.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | halfedge::{HalfEdgeImpl, HalfEdgeMeshImpl, HalfEdgeMeshType, HalfEdgeVertexImpl}, 3 | mesh::{DefaultEdgePayload, EdgeBasics, HalfEdge, MeshBasics}, 4 | }; 5 | 6 | impl HalfEdgeMeshImpl 7 | where 8 | T::EP: DefaultEdgePayload, 9 | { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/halfedge/mesh/builder/vertex.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | halfedge::{HalfEdgeImplMeshType, HalfEdgeMeshImpl}, 3 | math::{HasPosition, Vector3D}, 4 | mesh::{MeshBasics, MeshType3D, VertexBasics}, 5 | }; 6 | 7 | // TODO: Where to place this function? 8 | 9 | impl HalfEdgeMeshImpl { 10 | /// Flips the y and z coordinates of all vertices. 11 | pub fn flip_yz(&mut self) -> &mut Self { 12 | self.vertices_mut().for_each(|v| { 13 | let pos = v.payload().pos().xzy(); 14 | v.payload_mut().set_pos(pos) 15 | }); 16 | self 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/halfedge/mesh/halfedge.rs: -------------------------------------------------------------------------------- 1 | use super::HalfEdgeMeshImpl; 2 | use crate::{ 3 | halfedge::{HalfEdgeImplMeshType, BackwardEdgeIterator, ForwardEdgeIterator}, 4 | mesh::{HalfEdgeMesh, MeshBasics}, 5 | }; 6 | 7 | impl HalfEdgeMesh for HalfEdgeMeshImpl { 8 | #[allow(refining_impl_trait)] 9 | #[inline(always)] 10 | fn edges_from<'a>(&'a self, e: T::E) -> ForwardEdgeIterator<'a, T> { 11 | ForwardEdgeIterator::<'a, T>::new(self.edge(e).clone(), self) 12 | } 13 | 14 | #[allow(refining_impl_trait)] 15 | #[inline(always)] 16 | fn edges_back_from<'a>(&'a self, e: T::E) -> BackwardEdgeIterator<'a, T> { 17 | BackwardEdgeIterator::<'a, T>::new(self.edge(e).clone(), self) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/halfedge/mesh/mod.rs: -------------------------------------------------------------------------------- 1 | mod basics; 2 | mod builder; 3 | mod check; 4 | mod halfedge; 5 | mod pseudo_winged; 6 | 7 | use super::HalfEdgeImplMeshType; 8 | use crate::{ 9 | math::{HasNormal, Scalar, Transformable, Vector}, 10 | mesh::{ 11 | EuclideanMeshType, MeshTopology, MeshTrait, TransformableMesh, Triangulateable, WithNormals, 12 | }, 13 | util::DeletableVector, 14 | }; 15 | 16 | /// A halfedge-inspired mesh data structure for (open) manifold meshes. 17 | /// 18 | /// Since coordinates are a variable payload, you can use this mesh for any dimension >= 2. 19 | /// 20 | /// Non-manifold edges (multiple faces per edge) are currently not supported 21 | /// -- use multiple meshes or a "tufted cover". 22 | /// Non-manifold vertices are supported! 23 | /// 24 | /// Non-orientable surfaces have to be covered by multiple faces (so they become oriented). 25 | /// 26 | /// Currently only euclidean geometry is supported. 27 | #[derive(Clone)] 28 | pub struct HalfEdgeMeshImpl { 29 | // TODO: to import non-manifold edges, we could use the "tufted cover" https://www.cs.cmu.edu/~kmcrane/Projects/NonmanifoldLaplace/index.html 30 | // TODO: non-euclidean geometry 31 | vertices: DeletableVector, 32 | halfedges: DeletableVector, 33 | faces: DeletableVector, 34 | payload: T::MP, 35 | } 36 | 37 | impl HalfEdgeMeshImpl { 38 | /// Creates a new empty halfedge mesh 39 | pub fn new() -> Self { 40 | Self { 41 | vertices: DeletableVector::new(), 42 | halfedges: DeletableVector::new(), 43 | faces: DeletableVector::new(), 44 | payload: T::MP::default(), 45 | } 46 | } 47 | } 48 | 49 | impl Default for HalfEdgeMeshImpl { 50 | fn default() -> Self { 51 | Self::new() 52 | } 53 | } 54 | 55 | impl> TransformableMesh 56 | for HalfEdgeMeshImpl 57 | where 58 | T::VP: Transformable, 59 | T::EP: Transformable, 60 | T::FP: Transformable, 61 | T::MP: Transformable, 62 | { 63 | } 64 | impl< 65 | const D: usize, 66 | VecN: Vector, 67 | SN: Scalar, 68 | T: HalfEdgeImplMeshType + EuclideanMeshType>, 69 | > WithNormals for HalfEdgeMeshImpl 70 | { 71 | } 72 | impl MeshTopology for HalfEdgeMeshImpl {} 73 | impl Triangulateable for HalfEdgeMeshImpl {} 74 | impl MeshTrait for HalfEdgeMeshImpl { 75 | type T = T; 76 | } 77 | 78 | #[cfg(feature = "netsci")] 79 | impl crate::mesh::NetworkScience for HalfEdgeMeshImpl {} 80 | -------------------------------------------------------------------------------- /src/halfedge/mesh/pseudo_winged.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{ 4 | halfedge::HalfEdgeImplMeshType, 5 | math::IndexType, 6 | mesh::{EdgeBasics, HalfEdge, MeshBasics}, 7 | }; 8 | 9 | use super::HalfEdgeMeshImpl; 10 | 11 | /// A pseudo-winged edge representation of an edge for debugging purposes 12 | #[derive(Clone)] 13 | pub(crate) struct PseudoWingedEdge 14 | where 15 | E: IndexType, 16 | V: IndexType, 17 | F: IndexType, 18 | { 19 | id: E, 20 | twin: E, 21 | origin: V, 22 | target: V, 23 | prev: E, 24 | face: F, 25 | next: E, 26 | twin_prev: E, 27 | twin_face: F, 28 | twin_next: E, 29 | } 30 | 31 | impl std::fmt::Debug for PseudoWingedEdge { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | write!( 34 | f, 35 | "{: >w$} -{:->w$}--><--{:-w$} / {: w$} / {: HalfEdgeMeshImpl { 60 | /// Returns all edges as pseudo-winged edges 61 | pub(crate) fn pair_edges(&self) -> Vec> { 62 | let mut edges: HashMap> = HashMap::new(); 63 | self.edges().for_each(|edge| { 64 | let twin = edge.twin(self); 65 | if edges.contains_key(&twin.id()) { 66 | return; 67 | } 68 | edges.insert( 69 | edge.id(), 70 | PseudoWingedEdge { 71 | id: edge.id(), 72 | twin: twin.id(), 73 | origin: edge.origin_id(), 74 | target: twin.origin_id(), 75 | prev: edge.prev_id(), 76 | face: edge.face_id(), 77 | next: edge.next_id(), 78 | twin_prev: twin.prev_id(), 79 | twin_face: twin.face_id(), 80 | twin_next: twin.next_id(), 81 | }, 82 | ); 83 | }); 84 | 85 | let mut vec: Vec> = edges.values().cloned().collect(); 86 | vec.sort_by(|a, b| a.id.cmp(&b.id)); 87 | vec 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/halfedge/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a half-edge data structure for representing meshes. 2 | 3 | mod edge; 4 | mod face; 5 | mod mesh; 6 | mod primitives; 7 | mod vertex; 8 | 9 | pub use edge::*; 10 | pub use face::*; 11 | pub use mesh::*; 12 | pub use vertex::*; 13 | 14 | use crate::mesh::MeshType; 15 | 16 | /// This trait defines the associated types used in this half-edge mesh implementation and puts them into relation. 17 | pub trait HalfEdgeImplMeshType: 18 | MeshType< 19 | Mesh = HalfEdgeMeshImpl, 20 | Vertex = HalfEdgeVertexImpl, 21 | Edge = HalfEdgeImpl, 22 | Face = HalfEdgeFaceImpl, 23 | > 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/halfedge/primitives/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{HalfEdgeImplMeshType, HalfEdgeMeshImpl}; 2 | use crate::{ 3 | mesh::{ 4 | DefaultEdgePayload, DefaultFacePayload, EdgeBasics, EuclideanMeshType, FaceBasics, 5 | HalfEdge, MeshBasics, MeshBuilder, MeshPosition, MeshType3D, MeshTypeHalfEdge, 6 | }, 7 | operations::{MeshExtrude, MeshLoft, MeshSubdivision}, 8 | primitives::{Make2dShape, MakePlane, MakePrismatoid, MakeSphere}, 9 | }; 10 | 11 | impl> Make2dShape for HalfEdgeMeshImpl 12 | where 13 | T::EP: DefaultEdgePayload, 14 | T::FP: DefaultFacePayload, 15 | { 16 | fn insert_polygon(&mut self, vp: impl IntoIterator) -> T::E { 17 | // TODO: assertions 18 | let first = self.insert_loop(vp); 19 | self.close_hole(first, Default::default(), false); 20 | self.edge(first).twin_id() 21 | } 22 | 23 | fn insert_dihedron(&mut self, vp: impl IntoIterator) -> T::E { 24 | let first = self.insert_polygon(vp); 25 | self.close_hole(self.edge(first).twin_id(), Default::default(), false); 26 | first 27 | } 28 | } 29 | 30 | impl> MakePlane 31 | for HalfEdgeMeshImpl 32 | where 33 | T::EP: DefaultEdgePayload, 34 | T::FP: DefaultFacePayload, 35 | { 36 | } 37 | 38 | impl MakePrismatoid 39 | for HalfEdgeMeshImpl 40 | where 41 | T::EP: DefaultEdgePayload, 42 | T::FP: DefaultFacePayload, 43 | Self: Make2dShape, 44 | { 45 | } 46 | 47 | impl MakeSphere for HalfEdgeMeshImpl 48 | where 49 | T::EP: DefaultEdgePayload, 50 | T::FP: DefaultFacePayload, 51 | Self: Make2dShape, 52 | { 53 | } 54 | 55 | impl MeshSubdivision for HalfEdgeMeshImpl 56 | where 57 | T::EP: DefaultEdgePayload, 58 | T::Face: FaceBasics, 59 | T::Edge: HalfEdge + EdgeBasics, 60 | { 61 | } 62 | 63 | impl> MeshPosition 64 | for HalfEdgeMeshImpl 65 | { 66 | } 67 | 68 | impl MeshExtrude for HalfEdgeMeshImpl 69 | where 70 | T::EP: DefaultEdgePayload, 71 | T::FP: DefaultFacePayload, 72 | { 73 | } 74 | 75 | impl MeshLoft for HalfEdgeMeshImpl 76 | where 77 | T::EP: DefaultEdgePayload, 78 | T::FP: DefaultFacePayload, 79 | { 80 | } 81 | -------------------------------------------------------------------------------- /src/halfedge/vertex/basics.rs: -------------------------------------------------------------------------------- 1 | use super::{HalfEdgeImplMeshType, HalfEdgeVertexImpl, IncidentToVertexIterator}; 2 | use crate::{ 3 | math::IndexType, 4 | mesh::{EdgeBasics, HalfEdge, MeshBasics, VertexBasics}, 5 | }; 6 | 7 | impl VertexBasics for HalfEdgeVertexImpl { 8 | /// Returns the index of the vertex 9 | #[inline(always)] 10 | fn id(&self) -> T::V { 11 | self.id 12 | } 13 | 14 | /// Returns the payload of the vertex 15 | #[inline(always)] 16 | fn payload(&self) -> &T::VP { 17 | &self.payload 18 | } 19 | 20 | /// Returns a mutable reference to the payload of the vertex 21 | #[inline(always)] 22 | fn payload_mut(&mut self) -> &mut T::VP { 23 | &mut self.payload 24 | } 25 | 26 | /// Returns whether the vertex is a boundary vertex 27 | #[inline(always)] 28 | fn is_boundary(&self, mesh: &T::Mesh) -> bool { 29 | self.edges_out(mesh).any(|e| e.is_boundary(mesh)) 30 | } 31 | 32 | /* 33 | /// Returns whether the vertex is manifold 34 | #[inline(always)] 35 | fn is_manifold(&self) -> bool { 36 | self.next == IndexType::max() 37 | }*/ 38 | 39 | /// Returns whether the vertex has only one edge incident to it 40 | #[inline(always)] 41 | fn has_only_one_edge(&self, mesh: &T::Mesh) -> bool { 42 | // self.edges(mesh).count() == 1 43 | if let Some(e) = self.edge(mesh) { 44 | e.prev_id() == e.twin_id() 45 | } else { 46 | false 47 | } 48 | } 49 | 50 | /// Returns an outgoing half-edge incident to the vertex 51 | #[inline(always)] 52 | fn edge_id(&self, _mesh: &T::Mesh) -> T::E { 53 | self.edge 54 | } 55 | 56 | /// Returns an outgoing half-edge incident to the vertex 57 | #[inline(always)] 58 | fn edge(&self, mesh: &T::Mesh) -> Option { 59 | // PERF: avoid clone 60 | if self.edge == IndexType::max() { 61 | None 62 | } else { 63 | Some(mesh.edge(self.edge).clone()) 64 | } 65 | } 66 | 67 | /// Iterates all vertices adjacent to the vertex in the same manifold edge wheel (clockwise) 68 | #[inline(always)] 69 | fn vertices<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a { 70 | // TODO: slightly inefficient because of the clone and target being indirect 71 | self.edges_out(mesh).map(|e| e.target(mesh).clone()) 72 | } 73 | 74 | /// Iterates all faces adjacent to this vertex in the same manifold edge wheel (clockwise) 75 | #[inline(always)] 76 | fn faces<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a 77 | where 78 | T: 'a, 79 | { 80 | self.edges_out(mesh).filter_map(|e| e.face(mesh).cloned()) 81 | } 82 | 83 | #[inline(always)] 84 | fn edges_out<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a { 85 | if let Some(e) = self.edge(mesh) { 86 | IncidentToVertexIterator::::new(e, mesh) 87 | } else { 88 | return IncidentToVertexIterator::::empty(mesh); 89 | } 90 | } 91 | 92 | #[inline(always)] 93 | fn edges_in<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a { 94 | (if let Some(e) = self.edge(mesh) { 95 | IncidentToVertexIterator::::new(e, mesh) 96 | } else { 97 | IncidentToVertexIterator::::empty(mesh) 98 | }) 99 | .map(|e| e.twin(mesh)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/halfedge/vertex/iterator.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | halfedge::HalfEdgeImplMeshType, 3 | math::IndexType, 4 | mesh::{EdgeBasics, HalfEdge, MeshBasics}, 5 | }; 6 | 7 | /// Iterator over all half-edges incident to the same vertex (clockwise) 8 | pub struct IncidentToVertexIterator<'a, T: HalfEdgeImplMeshType + 'a> { 9 | is_first: bool, 10 | first: T::E, 11 | current: T::E, 12 | mesh: &'a T::Mesh, 13 | } 14 | 15 | impl<'a, T: HalfEdgeImplMeshType> IncidentToVertexIterator<'a, T> { 16 | /// Creates a new iterator 17 | pub fn new(first: T::Edge, mesh: &'a T::Mesh) -> Self { 18 | Self { 19 | first: first.id(), 20 | current: first.id(), 21 | mesh, 22 | is_first: true, 23 | } 24 | } 25 | 26 | /// Creates an empty iterator 27 | pub fn empty(mesh: &'a T::Mesh) -> Self { 28 | Self { 29 | first: IndexType::max(), 30 | current: IndexType::max(), 31 | mesh, 32 | is_first: true, 33 | } 34 | } 35 | } 36 | 37 | impl<'a, T: HalfEdgeImplMeshType> Iterator for IncidentToVertexIterator<'a, T> { 38 | type Item = T::Edge; 39 | 40 | fn next(&mut self) -> Option { 41 | if self.current == IndexType::max() { 42 | return None; 43 | } 44 | let current = self.mesh.edge(self.current); 45 | if self.is_first { 46 | self.is_first = false; 47 | return Some(current.clone()); 48 | } 49 | let next = current.twin(self.mesh).next(self.mesh); 50 | debug_assert!( 51 | next.origin_id() == self.mesh.edge(self.first).origin_id(), 52 | "The edge wheel around vertex {} is not closed. The mesh is invalid.", 53 | next.origin_id() 54 | ); 55 | if next.id() == self.first { 56 | return None; 57 | } else { 58 | self.current = next.id(); 59 | return Some(next); 60 | } 61 | } 62 | } 63 | 64 | /* 65 | /// Iterator over all vertices in the same non-manifold vertex wheel 66 | pub struct NonmanifoldVertexIterator<'a, T: HalfEdgeMeshType> { 67 | is_first: bool, 68 | first: T::V, 69 | current: Vertex, 70 | mesh: &'a T::Mesh, 71 | } 72 | 73 | impl<'a, T: HalfEdgeMeshType> NonmanifoldVertexIterator<'a, T> { 74 | /// Creates a new iterator 75 | pub fn new(first: Vertex, mesh: &'a T::Mesh) -> Self { 76 | Self { 77 | first: first.id(), 78 | current: first, 79 | mesh, 80 | is_first: true, 81 | } 82 | } 83 | } 84 | 85 | impl<'a, T: HalfEdgeMeshType> Iterator for NonmanifoldVertexIterator<'a, T> { 86 | type Item = Vertex; 87 | 88 | fn next(&mut self) -> Option { 89 | if self.is_first { 90 | self.is_first = false; 91 | Some(self.current.clone()) 92 | } else { 93 | if self.current.next == self.first { 94 | return None; 95 | } 96 | // PERF: avoid clone? 97 | self.current = self.mesh.vertex(self.current.next).clone(); 98 | Some(self.current.clone()) 99 | } 100 | } 101 | } 102 | */ 103 | -------------------------------------------------------------------------------- /src/halfedge/vertex/mod.rs: -------------------------------------------------------------------------------- 1 | mod basics; 2 | mod iterator; 3 | 4 | pub use iterator::*; 5 | 6 | use super::HalfEdgeImplMeshType; 7 | use crate::{ 8 | math::IndexType, 9 | mesh::{DefaultVertexPayload, HalfEdgeVertex, MeshType, Vertex, VertexBasics, VertexPayload}, 10 | util::Deletable, 11 | }; 12 | 13 | /// A vertex in a mesh. 14 | #[derive(Clone)] 15 | pub struct HalfEdgeVertexImpl { 16 | /// the index of the vertex 17 | id: T::V, 18 | 19 | /// An outgoing half-edge incident to the vertex. 20 | edge: T::E, 21 | 22 | /* 23 | /// Since we support non-manifold vertices, there can be a "wheel" of vertices, 24 | /// each connected to its own "wheel" of manifold edges. 25 | /// Will be IndexType::max() if the vertex is manifold. 26 | /// TODO: This is only necessary for non-manifold vertices where there are multiple next-prev wheels. But even with one wheel, this can be non-manifold if the vertex is singular. 27 | next: V, 28 | */ 29 | /// the payload of the vertex 30 | payload: T::VP, 31 | } 32 | 33 | impl HalfEdgeVertexImpl { 34 | /// Creates a new vertex 35 | pub fn new(edge: T::E, payload: T::VP) -> Self { 36 | //assert!(edge != IndexType::max()); 37 | Self { 38 | id: IndexType::max(), 39 | edge, 40 | //next: IndexType::max(), 41 | payload, 42 | } 43 | } 44 | } 45 | 46 | impl HalfEdgeVertex for HalfEdgeVertexImpl { 47 | fn set_edge(&mut self, edge: T::E) { 48 | self.edge = edge; 49 | } 50 | } 51 | 52 | impl Vertex for HalfEdgeVertexImpl 53 | where 54 | T: MeshType>, 55 | { 56 | type T = T; 57 | } 58 | 59 | impl std::fmt::Debug for HalfEdgeVertexImpl { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | write!( 62 | f, 63 | "{: >w$}) -{:-^w$}->; payload: {:?}", 64 | self.id().index(), 65 | self.edge.index(), 66 | self.payload, 67 | w = 3 68 | ) 69 | } 70 | } 71 | 72 | impl Deletable for HalfEdgeVertexImpl { 73 | fn delete(&mut self) { 74 | assert!(self.id != IndexType::max()); 75 | self.id = IndexType::max(); 76 | } 77 | 78 | fn is_deleted(&self) -> bool { 79 | self.id == IndexType::max() 80 | } 81 | 82 | fn set_id(&mut self, id: T::V) { 83 | assert!(self.id == IndexType::max()); 84 | assert!(id != IndexType::max()); 85 | self.id = id; 86 | } 87 | 88 | fn allocate() -> Self { 89 | Self { 90 | id: IndexType::max(), 91 | edge: IndexType::max(), 92 | payload: T::VP::allocate(), 93 | } 94 | } 95 | } 96 | 97 | impl Default for HalfEdgeVertexImpl 98 | where 99 | T::VP: DefaultVertexPayload, 100 | { 101 | /// Creates a deleted vertex 102 | fn default() -> Self { 103 | Self { 104 | id: IndexType::max(), 105 | edge: IndexType::max(), 106 | //next: IndexType::max(), 107 | payload: T::VP::default(), 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![doc = include_str!("../README.md")] 3 | #![doc = include_str!("../doc/start.md")] 4 | 5 | pub mod extensions; 6 | pub mod halfedge; 7 | pub mod math; 8 | pub mod mesh; 9 | pub mod operations; 10 | pub mod primitives; 11 | pub mod tesselate; 12 | pub mod util; 13 | 14 | /// A prelude for easy importing of commonly used types and traits. 15 | pub mod prelude { 16 | pub use crate::halfedge::*; 17 | pub use crate::math::*; 18 | pub use crate::mesh::*; 19 | pub use crate::operations::*; 20 | pub use crate::primitives::*; 21 | pub use crate::tesselate::*; 22 | pub use crate::util::*; 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use crate::prelude::*; 28 | 29 | #[test] 30 | #[cfg(feature = "bevy")] 31 | fn test_library_bevy() { 32 | use crate::extensions::bevy::*; 33 | 34 | let mut mesh = BevyMesh3d::geodesic_octahedron(3.0, 128); 35 | let mut meta = TesselationMeta::default(); 36 | mesh.generate_smooth_normals(); 37 | let (_is, _vs) = mesh.triangulate_and_generate_flat_normals_post( 38 | TriangulationAlgorithm::Delaunay, 39 | &mut meta, 40 | ); 41 | // TODO: test something 42 | } 43 | 44 | #[test] 45 | #[cfg(feature = "nalgebra")] 46 | fn test_library_nalgebra() { 47 | use crate::extensions::nalgebra::*; 48 | 49 | let mut mesh = Mesh3d64::geodesic_octahedron(3.0, 128); 50 | let mut meta = TesselationMeta::default(); 51 | mesh.generate_smooth_normals(); 52 | let (_is, _vs) = mesh.triangulate_and_generate_flat_normals_post( 53 | TriangulationAlgorithm::Delaunay, 54 | &mut meta, 55 | ); 56 | // TODO: test something 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/math/impls/f32.rs: -------------------------------------------------------------------------------- 1 | //! Plain f32 implementation of the mathematical traits. 2 | 3 | use crate::math::{Rotator, Scalar, Vector2D}; 4 | 5 | impl Scalar for f32 { 6 | const PI: Self = std::f32::consts::PI; 7 | const EPS: Self = std::f32::EPSILON; 8 | const ZERO: Self = 0.0; 9 | const ONE: Self = 1.0; 10 | const TWO: Self = 2.0; 11 | const THREE: Self = 3.0; 12 | const FOUR: Self = 4.0; 13 | const FIVE: Self = 5.0; 14 | const TEN: Self = 10.0; 15 | const HALF: Self = 0.5; 16 | const PHI: Self = 1.61803398874989484820; 17 | const INFINITY: Self = std::f32::INFINITY; 18 | const NEG_INFINITY: Self = std::f32::NEG_INFINITY; 19 | 20 | #[inline(always)] 21 | fn is_positive(self) -> bool { 22 | self.is_sign_positive() 23 | } 24 | 25 | #[inline(always)] 26 | fn is_negative(self) -> bool { 27 | self.is_sign_negative() 28 | } 29 | 30 | #[inline(always)] 31 | fn acos(self) -> Self { 32 | f32::acos(self) 33 | } 34 | 35 | #[inline(always)] 36 | fn sin(&self) -> Self { 37 | f32::sin(*self) 38 | } 39 | 40 | #[inline(always)] 41 | fn cos(&self) -> Self { 42 | f32::cos(*self) 43 | } 44 | 45 | #[inline(always)] 46 | fn tan(&self) -> Self { 47 | f32::tan(*self) 48 | } 49 | 50 | #[inline(always)] 51 | fn cot(&self) -> Self { 52 | self.tan().recip() 53 | } 54 | 55 | #[inline(always)] 56 | fn atan2(&self, other: Self) -> Self { 57 | f32::atan2(*self, other) 58 | } 59 | 60 | #[inline(always)] 61 | fn to_f64(self) -> f64 { 62 | self as f64 63 | } 64 | 65 | #[inline(always)] 66 | fn from_usize(value: usize) -> Self { 67 | value as f32 68 | } 69 | 70 | #[inline(always)] 71 | fn max(&self, b: Self) -> Self { 72 | f32::max(*self, b) 73 | } 74 | 75 | #[inline(always)] 76 | fn min(&self, b: Self) -> Self { 77 | f32::min(*self, b) 78 | } 79 | 80 | #[inline(always)] 81 | fn sqrt(self) -> Self { 82 | f32::sqrt(self) 83 | } 84 | 85 | #[inline(always)] 86 | fn is_finite(self) -> bool { 87 | f32::is_finite(self) 88 | } 89 | 90 | #[inline(always)] 91 | fn is_nan(self) -> bool { 92 | f32::is_nan(self) 93 | } 94 | } 95 | 96 | impl> Rotator for f32 {} 97 | -------------------------------------------------------------------------------- /src/math/impls/f64.rs: -------------------------------------------------------------------------------- 1 | //! Plain f64 implementation of the mathematical traits. 2 | 3 | use crate::math::{Rotator, Scalar, Vector2D}; 4 | 5 | impl Scalar for f64 { 6 | const PI: Self = std::f64::consts::PI; 7 | const EPS: Self = std::f64::EPSILON; 8 | const ZERO: Self = 0.0; 9 | const ONE: Self = 1.0; 10 | const TWO: Self = 2.0; 11 | const THREE: Self = 3.0; 12 | const FOUR: Self = 4.0; 13 | const FIVE: Self = 5.0; 14 | const TEN: Self = 10.0; 15 | const HALF: Self = 0.5; 16 | const PHI: Self = 1.61803398874989484820; 17 | const INFINITY: Self = std::f64::INFINITY; 18 | const NEG_INFINITY: Self = std::f64::NEG_INFINITY; 19 | 20 | #[inline(always)] 21 | fn is_positive(self) -> bool { 22 | self.is_sign_positive() 23 | } 24 | 25 | #[inline(always)] 26 | fn is_negative(self) -> bool { 27 | self.is_sign_negative() 28 | } 29 | 30 | #[inline(always)] 31 | fn acos(self) -> Self { 32 | f64::acos(self) 33 | } 34 | 35 | #[inline(always)] 36 | fn sin(&self) -> Self { 37 | f64::sin(*self) 38 | } 39 | 40 | #[inline(always)] 41 | fn cos(&self) -> Self { 42 | f64::cos(*self) 43 | } 44 | 45 | #[inline(always)] 46 | fn tan(&self) -> Self { 47 | f64::tan(*self) 48 | } 49 | 50 | #[inline(always)] 51 | fn cot(&self) -> Self { 52 | self.tan().recip() 53 | } 54 | 55 | #[inline(always)] 56 | fn atan2(&self, other: Self) -> Self { 57 | f64::atan2(*self, other) 58 | } 59 | 60 | #[inline(always)] 61 | fn to_f64(self) -> f64 { 62 | self 63 | } 64 | 65 | #[inline(always)] 66 | fn from_usize(value: usize) -> Self { 67 | value as f64 68 | } 69 | 70 | #[inline(always)] 71 | fn max(&self, b: Self) -> Self { 72 | f64::max(*self, b) 73 | } 74 | 75 | #[inline(always)] 76 | fn min(&self, b: Self) -> Self { 77 | f64::min(*self, b) 78 | } 79 | 80 | #[inline(always)] 81 | fn sqrt(self) -> Self { 82 | f64::sqrt(self) 83 | } 84 | 85 | #[inline(always)] 86 | fn is_finite(self) -> bool { 87 | f64::is_finite(self) 88 | } 89 | 90 | #[inline(always)] 91 | fn is_nan(self) -> bool { 92 | f64::is_nan(self) 93 | } 94 | } 95 | 96 | impl> Rotator for f64 {} 97 | -------------------------------------------------------------------------------- /src/math/impls/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementations of the mathematical traits for various types. 2 | 3 | pub mod f32; 4 | pub mod f64; 5 | 6 | // pub mod mpfr; 7 | // pub mod fixed; 8 | -------------------------------------------------------------------------------- /src/math/index_type.rs: -------------------------------------------------------------------------------- 1 | /// based on petgraph::csr::IndexType; 2 | 3 | /// Trait for the unsigned integer type used for node and edge indices. 4 | pub trait IndexType: 5 | Copy 6 | + Default 7 | + std::hash::Hash 8 | + Ord 9 | + std::fmt::Debug 10 | + 'static 11 | + std::fmt::Display 12 | + num_traits::Zero 13 | { 14 | /// Create a new index from a usize. Panics if the usize is out of range. 15 | fn new(x: usize) -> Self; 16 | 17 | /// Convert the index to a usize. 18 | fn index(&self) -> usize; 19 | 20 | /// Return the maximum value of the index type. 21 | fn max() -> Self; 22 | } 23 | 24 | impl IndexType for usize { 25 | #[inline(always)] 26 | fn new(x: usize) -> Self { 27 | x 28 | } 29 | 30 | #[inline(always)] 31 | fn index(&self) -> Self { 32 | *self 33 | } 34 | 35 | #[inline(always)] 36 | fn max() -> Self { 37 | ::std::usize::MAX 38 | } 39 | } 40 | 41 | impl IndexType for u32 { 42 | #[inline(always)] 43 | fn new(x: usize) -> Self { 44 | assert!(x <= ::std::u32::MAX as usize, "Index out of range: {}", x); 45 | x as u32 46 | } 47 | 48 | #[inline(always)] 49 | fn index(&self) -> usize { 50 | *self as usize 51 | } 52 | 53 | #[inline(always)] 54 | fn max() -> Self { 55 | ::std::u32::MAX 56 | } 57 | } 58 | 59 | impl IndexType for u16 { 60 | #[inline(always)] 61 | fn new(x: usize) -> Self { 62 | assert!(x <= ::std::u16::MAX as usize, "Index out of range: {}", x); 63 | x as u16 64 | } 65 | 66 | #[inline(always)] 67 | fn index(&self) -> usize { 68 | *self as usize 69 | } 70 | 71 | #[inline(always)] 72 | fn max() -> Self { 73 | ::std::u16::MAX 74 | } 75 | } 76 | 77 | impl IndexType for u8 { 78 | #[inline(always)] 79 | fn new(x: usize) -> Self { 80 | assert!(x <= ::std::u8::MAX as usize, "Index out of range: {}", x); 81 | x as u8 82 | } 83 | 84 | #[inline(always)] 85 | fn index(&self) -> usize { 86 | *self as usize 87 | } 88 | 89 | #[inline(always)] 90 | fn max() -> Self { 91 | ::std::u8::MAX 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/math/line_segment.rs: -------------------------------------------------------------------------------- 1 | //! Line segments in 2d space. 2 | 3 | use super::{Scalar, Vector2D}; 4 | 5 | /// Trait for line segments in 2d space. 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct LineSegment2D { 8 | start: Vec2, 9 | end: Vec2, 10 | } 11 | 12 | impl LineSegment2D { 13 | /// Creates a new line segment from two points. 14 | #[inline(always)] 15 | pub fn new(start: Vec2, end: Vec2) -> Self { 16 | Self { start, end } 17 | } 18 | 19 | /// Returns the start point of the line segment. 20 | #[inline(always)] 21 | pub fn start(&self) -> Vec2 { 22 | self.start 23 | } 24 | 25 | /// Returns the end point of the line segment. 26 | #[inline(always)] 27 | pub fn end(&self) -> Vec2 { 28 | self.end 29 | } 30 | 31 | /// Returns the length of the line segment. 32 | #[inline(always)] 33 | pub fn length(&self) -> Vec2::S { 34 | self.start().distance(&self.end()) 35 | } 36 | 37 | /// Returns the squared length of the line segment. 38 | #[inline(always)] 39 | pub fn length_squared(&self) -> Vec2::S { 40 | self.start().distance_squared(&self.end()) 41 | } 42 | 43 | /// Returns the midpoint of the line segment. 44 | #[inline(always)] 45 | pub fn midpoint(&self) -> Vec2 { 46 | self.start() + (self.end() - self.start()) * Vec2::S::HALF 47 | } 48 | 49 | /// Returns the direction of the line segment. 50 | #[inline(always)] 51 | pub fn direction(&self) -> Vec2 { 52 | (self.end() - self.start()).normalize() 53 | } 54 | 55 | /// Returns the intersection point of two line segments. 56 | /// `eps` is the epsilon for the cross product, i.e., for whether the lines are considered parallel. 57 | /// `eps2` is the epsilon for the t and u values, i.e., for the line length. 58 | pub fn intersect_line(&self, other: &Self, eps: Vec2::S, eps2: Vec2::S) -> Option { 59 | let r = self.end() - self.start(); 60 | let s = other.end() - other.start(); 61 | let rxs = r.perp_dot(&s); 62 | if rxs.abs() <= eps { 63 | // Lines are parallel or coincident 64 | return None; 65 | } 66 | let q_p = other.start() - self.start(); 67 | 68 | let t = q_p.perp_dot(&s) / rxs; 69 | if t < -eps2 || t > eps2 + 1.0.into() { 70 | return None; 71 | } 72 | 73 | let u = q_p.perp_dot(&r) / rxs; 74 | if u < -eps2 || u > eps2 + 1.0.into() { 75 | return None; 76 | } 77 | 78 | Some(self.start() + r * t) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! Traits to define the geometric primitives and operations used in the library. 2 | 3 | pub mod impls; 4 | mod index_type; 5 | mod line_segment; 6 | mod polygon; 7 | mod position; 8 | mod quaternion; 9 | mod scalar; 10 | mod transform; 11 | mod transformable; 12 | mod vector; 13 | mod vector2d; 14 | mod vector3d; 15 | mod vector4d; 16 | mod zero; 17 | 18 | pub use index_type::*; 19 | pub use line_segment::*; 20 | pub use polygon::*; 21 | pub use position::*; 22 | pub use quaternion::*; 23 | pub use scalar::*; 24 | pub use transform::*; 25 | pub use transformable::*; 26 | pub use vector::*; 27 | pub use vector2d::*; 28 | pub use vector3d::*; 29 | pub use vector4d::*; 30 | pub use zero::*; 31 | -------------------------------------------------------------------------------- /src/math/position.rs: -------------------------------------------------------------------------------- 1 | use super::{Scalar, Vector}; 2 | 3 | /// Indicates that the vertex payload has a position vector. 4 | pub trait HasPosition> { 5 | /// The scalar type of the coordinates used in the payload. Mainly to choose between f32 and f64. But could also work with fixed point etc... 6 | type S: Scalar; 7 | 8 | /// Get the position vector of the payload. 9 | fn pos(&self) -> &Vec; 10 | 11 | /// Creates a payload from a vector. 12 | fn from_pos(v: Vec) -> Self; 13 | 14 | /// Set the position vector of the payload. 15 | fn set_pos(&mut self, v: Vec); 16 | } 17 | 18 | /// Indicates that the vertex payload has a normal vector. 19 | pub trait HasNormal> { 20 | /// The scalar type of the coordinates used in the payload. Mainly to choose between f32 and f64. But could also work with fixed point etc... 21 | type S: Scalar; 22 | 23 | /// returns the normals of the payload 24 | fn normal(&self) -> &Vec; 25 | 26 | /// Sets the normals. 27 | fn set_normal(&mut self, normal: Vec); 28 | } 29 | 30 | /// Indicates that the vertex payload has a uv coordinate vector. These coordinates are always 2D. 31 | pub trait HasUV> { 32 | /// The scalar type of the coordinates used in the payload. Mainly to choose between f32 and f64. But could also work with fixed point etc... 33 | type S: Scalar; 34 | 35 | /// returns the uv coordinates of the payload 36 | fn uv(&self) -> &Vec; 37 | 38 | /// Sets the uv coordinates. 39 | fn set_uv(&mut self, normal: Vec); 40 | } 41 | -------------------------------------------------------------------------------- /src/math/quaternion.rs: -------------------------------------------------------------------------------- 1 | use super::{Scalar, Vector3D, Vector4D}; 2 | 3 | /// Trait for quarternions. 4 | pub trait Quarternion: Clone + Copy + Default + std::fmt::Debug + 'static { 5 | /// The scalar type of the coordinates and angles used in the rotation. 6 | type S: Scalar; 7 | 8 | /// The 3d vector type used in the rotation. 9 | type Vec3: Vector3D; 10 | 11 | /// The 4d vector type used in the rotation. 12 | type Vec4: Vector4D; 13 | 14 | /// Returns the identity rotation. 15 | fn identity() -> Self; 16 | 17 | /// Returns a rotation from a rotation arc. 18 | fn from_rotation_arc(from: Self::Vec3, to: Self::Vec3) -> Self; 19 | 20 | /// Returns a rotation from an axis and an angle. 21 | fn from_axis_angle(axis: Self::Vec3, angle: Self::S) -> Self; 22 | 23 | /// Returns the axis and angle of the rotation. 24 | fn axis_angle(&self) -> (Self::Vec3, Self::S); 25 | 26 | /// Returns the matrix representation of the rotation. 27 | fn vec4(&self) -> Self::Vec4; 28 | } 29 | -------------------------------------------------------------------------------- /src/math/transform.rs: -------------------------------------------------------------------------------- 1 | use super::{Scalar, Vector}; 2 | 3 | /// Trait for the data structure needed to rotate the value of type V. 4 | pub trait Rotator: Clone {} 5 | 6 | // TODO: use references to vectors instead! 7 | 8 | /// Trait for tansformations in nd space. We call it `TransformTrait` to avoid 9 | /// collisions with the `Transform` struct in Bevy. 10 | 11 | pub trait TransformTrait: 12 | Clone + Copy + Default + std::fmt::Debug + 'static 13 | { 14 | /// The vector type used in the transformatiom. 15 | type Vec: Vector; 16 | 17 | /// The rotation type used in the transformation. 18 | type Rot: Rotator; 19 | 20 | /// Returns the identity transformation. 21 | fn identity() -> Self; 22 | 23 | /// Constructs a transform from a rotation. 24 | fn from_rotation(r: Self::Rot) -> Self; 25 | 26 | /// Constructs a transform from a rotation arc. 27 | fn from_rotation_arc(from: Self::Vec, to: Self::Vec) -> Self; 28 | 29 | /// Constructs a transform from a translation. 30 | fn from_translation(v: Self::Vec) -> Self; 31 | 32 | /// Constructs a transform from a scale. 33 | fn from_scale(v: Self::Vec) -> Self; 34 | 35 | /// Adds scale (everything is scaled - also previous translations). 36 | fn with_scale(&self, v: Self::Vec) -> Self; 37 | 38 | /// Adds translation. 39 | fn with_translation(&self, v: Self::Vec) -> Self; 40 | 41 | /// Applies the rotation/translation/scale/sheer to a vector. 42 | fn apply(&self, v: Self::Vec) -> Self::Vec; 43 | 44 | /// Applies the rotation/scale/sheer to a vector. 45 | fn apply_vec(&self, v: Self::Vec) -> Self::Vec; 46 | 47 | /// Chains two transformations. First apply the left transformation, then the other. 48 | fn chain(&self, other: &Self) -> Self; 49 | } 50 | -------------------------------------------------------------------------------- /src/math/transformable.rs: -------------------------------------------------------------------------------- 1 | use super::{Rotator, Scalar, TransformTrait, Vector}; 2 | 3 | /// A trait that defines how a vertex payload can be linearly transformed. 4 | pub trait Transformable: Sized + Clone { 5 | /// The transformation type used in the payload. 6 | type Trans: TransformTrait; 7 | 8 | /// The rotation type used in the payload. 9 | type Rot: Rotator; 10 | 11 | /// The vector type used in the payload. 12 | type Vec: Vector; 13 | 14 | /// The scalar type of the coordinates used in the payload. Mainly to choose between f32 and f64. But could also work with integers etc... 15 | type S: Scalar; 16 | 17 | /// Returns the coordinates of the payload as a reference. 18 | fn transformed(&self, t: &Self::Trans) -> Self { 19 | let mut c = self.clone(); 20 | c.transform(t); 21 | c 22 | } 23 | 24 | /// Returns a translated clone of the payload. 25 | fn translated(&self, v: &Self::Vec) -> Self { 26 | let mut c = self.clone(); 27 | c.translate(v); 28 | c 29 | } 30 | 31 | /// Returns the scaled clone of the payload. 32 | fn scaled(&self, s: &Self::Vec) -> Self { 33 | let mut c = self.clone(); 34 | c.scale(s); 35 | c 36 | } 37 | 38 | /// Returns the rotated clone of the payload. 39 | fn rotated(&self, r: &Self::Rot) -> Self { 40 | let mut c = self.clone(); 41 | c.rotate(r); 42 | c 43 | } 44 | 45 | /// Interpolates between two payloads. 46 | fn lerped(&self, other: &Self, t: Self::S) -> Self { 47 | let mut c = self.clone(); 48 | c.lerp(other, t); 49 | c 50 | } 51 | 52 | /// Returns the coordinates of the payload as a reference. 53 | fn transform(&mut self, t: &Self::Trans) -> &mut Self; 54 | 55 | /// Returns a translated clone of the payload. 56 | fn translate(&mut self, v: &Self::Vec) -> &mut Self { 57 | self.transform(&Self::Trans::from_translation(*v)) 58 | } 59 | 60 | /// Returns the scaled clone of the payload. 61 | fn scale(&mut self, s: &Self::Vec) -> &mut Self { 62 | self.transform(&Self::Trans::from_scale(*s)) 63 | } 64 | 65 | /// Returns the rotated clone of the payload. 66 | fn rotate(&mut self, r: &Self::Rot) -> &mut Self { 67 | self.transform(&Self::Trans::from_rotation(r.clone())) 68 | } 69 | 70 | /// Interpolates between two payloads. 71 | fn lerp(&mut self, other: &Self, t: Self::S) -> &mut Self; 72 | } 73 | -------------------------------------------------------------------------------- /src/math/vector.rs: -------------------------------------------------------------------------------- 1 | use super::{kahan_summation, HasZero, Scalar, Vector2D}; 2 | 3 | /// Trait for coordinates in n-dimensional space. 4 | pub trait Vector: 5 | Copy 6 | + PartialEq 7 | + std::fmt::Debug 8 | + std::ops::Add 9 | + std::ops::AddAssign 10 | + std::ops::Sub 11 | + std::ops::SubAssign 12 | //+ std::ops::Mul 13 | + std::ops::Mul 14 | //+ std::ops::MulAssign 15 | + std::ops::Div 16 | + std::ops::Neg 17 | + HasZero 18 | + 'static 19 | { 20 | /// Returns the distance between two points. 21 | fn distance(&self, other: &Self) -> S; 22 | 23 | /// Returns the squared distance between two points. 24 | fn distance_squared(&self, other: &Self) -> S; 25 | 26 | /// Length of the vector 27 | fn length(&self) -> S; 28 | 29 | /// Squared length of the vector 30 | fn length_squared(&self) -> S; 31 | 32 | /// Returns the dot product of two vectors. 33 | fn dot(&self, other: &Self) -> S; 34 | 35 | /// Returns the x-coordinate. 36 | fn x(&self) -> S; 37 | 38 | /// Returns the y-coordinate. (or 0 if not present) 39 | fn y(&self) -> S; 40 | 41 | /// Returns the z-coordinate. (or 0 if not present) 42 | fn z(&self) -> S; 43 | 44 | /// Returns the w-coordinate. (or 0 if not present) 45 | fn w(&self) -> S; 46 | 47 | /// Returns the coordinates as a tuple. 48 | fn vec2>(&self) -> Vec2 { 49 | Vec2::new(self.x(), self.y()) 50 | } 51 | 52 | /// Create a vector from one coordinate 53 | fn from_x(x: S) -> Self; 54 | 55 | /// Create a vector from two coordinates. Drops the y-coordinate if not present. 56 | fn from_xy(x: S, y: S) -> Self; 57 | 58 | /// Create a vector from three coordinates. Drops the y- and z-coordinate if not present. 59 | fn from_xyz(x: S, y: S, z: S) -> Self; 60 | 61 | /// Normalizes the vector. Panics if the vector is the zero vector. 62 | fn normalize(&self) -> Self; 63 | 64 | /// Creates a vector with all the same coordinates. 65 | fn splat(value: S) -> Self; 66 | 67 | /// Calculate the sum of an iterator of vectors using some numerically stable algorithm. 68 | fn stable_sum>(iter: I) -> Self { 69 | kahan_summation(iter).0 70 | } 71 | 72 | /// Calculate the mean of an iterator of vectors using some numerically stable algorithm. 73 | fn stable_mean>(iter: I) -> Self { 74 | let (sum, count) = kahan_summation(iter); 75 | sum / S::from_usize(count) 76 | } 77 | 78 | /// Returns the angle of rotation (in radians) from self to rhs in the range [0, +π] resp. [-π, +π] for 2d. 79 | fn angle_between(&self, other: Self) -> S { 80 | let len_self = self.length(); 81 | let len_other = other.length(); 82 | 83 | if len_self.is_about(S::ZERO, S::EPS) || len_other.is_about(S::ZERO, S::EPS) { 84 | // Angle is undefined for zero-length vectors; handle as needed 85 | return S::ZERO; 86 | } 87 | 88 | if D == 2 { 89 | let det = self.x() * other.y() - self.y() * other.x(); 90 | det.atan2(self.dot(&other)) 91 | } else { 92 | let cos_theta = self.dot(&other) / (len_self * len_other); 93 | 94 | // Clamp cos_theta to [-1, 1] to handle numerical inaccuracies 95 | cos_theta.clamp(-S::ONE, S::ONE).acos() 96 | } 97 | } 98 | 99 | /// Check if two vectors are approximately equal. 100 | fn is_about(&self, other: &Self, epsilon: S) -> bool; 101 | 102 | /// Returns the zero vector. 103 | fn zero() -> Self { 104 | Self::splat(S::ZERO) 105 | } 106 | } 107 | 108 | /// Additional methods for vector iterators. 109 | pub trait VectorIteratorExt>: 110 | Iterator 111 | { 112 | /// Calculate the sum of an iterator of vectors using some numerically stable algorithm. 113 | fn stable_sum(self) -> Self::Item 114 | where 115 | Self: Sized, 116 | { 117 | V::stable_sum(self) 118 | } 119 | 120 | /// Calculate the mean of an iterator of vectors using some numerically stable algorithm. 121 | fn stable_mean(self) -> Self::Item 122 | where 123 | Self: Sized, 124 | { 125 | V::stable_mean(self) 126 | } 127 | } 128 | 129 | impl, S: Scalar, const D: usize, V: Vector> VectorIteratorExt 130 | for I 131 | { 132 | } 133 | -------------------------------------------------------------------------------- /src/math/vector2d.rs: -------------------------------------------------------------------------------- 1 | use super::{Scalar, Vector}; 2 | 3 | /// Trait for coordinates in 2d space. 4 | pub trait Vector2D: Vector { 5 | /// The scalar type of the coordinates used in the vector 6 | type S: Scalar; 7 | 8 | /// Construct from scalar values. 9 | fn new(x: Self::S, y: Self::S) -> Self; 10 | 11 | /// True iff the vertex curr is a convex corner. 12 | /// Assume counter-clockwise vertex order. 13 | #[inline(always)] 14 | fn convex(&self, prev: Self, next: Self) -> bool { 15 | // TODO: Numerical robustness 16 | (*self - prev).perp_dot(&(next - *self)).is_positive() 17 | } 18 | 19 | /// True if the vertex is collinear. 20 | fn collinear(&self, a: Self, b: Self, eps: Self::S) -> bool { 21 | let ab = b - a; 22 | let ac = *self - a; 23 | ab.perp_dot(&ac).abs() <= eps 24 | } 25 | 26 | /// Angle between the segment from self to a and the segment from self to b. 27 | fn angle_tri(&self, a: Self, b: Self) -> Self::S { 28 | Vector::angle_between(&(a - *self), b - *self) 29 | } 30 | 31 | /// Returns the barycentric sign of a point in a triangle. 32 | #[inline(always)] 33 | fn barycentric_sign(a: Self, b: Self, c: Self) -> Self::S { 34 | (a - c).perp_dot(&(b - c)) 35 | } 36 | 37 | /// Returns the cross product (perpendicular dot product) of two 2d vectors. 38 | #[inline(always)] 39 | fn perp_dot(&self, other: &Self) -> Self::S { 40 | self.x() * other.y() - self.y() * other.x() 41 | } 42 | 43 | /// Whether the point is inside the triangle. 44 | #[inline(always)] 45 | fn is_inside_triangle(&self, a: Self, b: Self, c: Self) -> bool { 46 | // TODO: Numerical robustness 47 | // TODO: Possible remove this 48 | let bs1 = Self::barycentric_sign(*self, a, b); 49 | let bs2 = Self::barycentric_sign(*self, b, c); 50 | let bs3 = Self::barycentric_sign(*self, c, a); 51 | let inside_ccw = bs1.is_positive() && bs2.is_positive() && bs3.is_positive(); 52 | let inside_cw = bs1.is_negative() && bs2.is_negative() && bs3.is_negative(); 53 | inside_ccw || inside_cw 54 | } 55 | 56 | /// Swizzle 57 | fn xy(&self) -> Self { 58 | Self::new(self.x(), self.y()) 59 | } 60 | 61 | /// Swizzle 62 | fn yx(&self) -> Self { 63 | Self::new(self.y(), self.x()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/math/vector4d.rs: -------------------------------------------------------------------------------- 1 | use super::{Scalar, Vector}; 2 | 3 | /// Trait for coordinates in 3d space. 4 | pub trait Vector4D: Vector { 5 | /// The scalar type of the coordinates used in the vector 6 | type S: Scalar; 7 | 8 | /// Construct from scalar values. 9 | fn new(x: Self::S, y: Self::S, z: Self::S, w: Self::S) -> Self; 10 | 11 | /// Convert to an array. 12 | fn to_array(&self) -> [Self::S; 4] { 13 | [self.x(), self.y(), self.z(), self.w()] 14 | } 15 | 16 | /// Returns the coordinate values as a tuple. 17 | fn tuple(&self) -> (Self::S, Self::S, Self::S, Self::S) { 18 | (self.x(), self.y(), self.z(), self.w()) 19 | } 20 | 21 | /// Swizzle 22 | fn xyzw(&self) -> Self { 23 | Self::new(self.x(), self.y(), self.z(), self.w()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/math/zero.rs: -------------------------------------------------------------------------------- 1 | use super::Scalar; 2 | 3 | /// Trait for types that have a zero value. 4 | pub trait HasZero { 5 | /// Returns the zero value for this type. 6 | fn zero() -> Self; 7 | 8 | /// Returns whether this value is zero. 9 | fn is_zero(&self) -> bool; 10 | } 11 | 12 | impl HasZero for T { 13 | #[inline(always)] 14 | fn zero() -> Self { 15 | T::zero() 16 | } 17 | 18 | #[inline(always)] 19 | fn is_zero(&self) -> bool { 20 | self.is_zero() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/mesh/edge/basics.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::Scalar, 3 | mesh::{EuclideanMeshType, MeshType, VertexBasics}, 4 | }; 5 | 6 | /// Basic edge traits for a mesh. Can be directed or undirected. 7 | pub trait EdgeBasics>: std::fmt::Debug + Clone { 8 | /// Returns the identifier of the edge 9 | fn id(&self) -> T::E; 10 | 11 | /// Returns the face payload. 12 | fn payload(&self) -> &T::EP; 13 | 14 | /// Returns a mutable reference to the face payload. 15 | fn payload_mut(&mut self) -> &mut T::EP; 16 | 17 | /// Returns the source vertex of the edge. If it is not directed, can be either vertex but not the same as the target. 18 | fn origin<'a>(&'a self, mesh: &'a T::Mesh) -> &'a T::Vertex; 19 | 20 | /// Returns the target vertex of the edge. If it is not directed, can be either vertex but not the same as the origin. 21 | fn target<'a>(&'a self, mesh: &'a T::Mesh) -> &'a T::Vertex; 22 | 23 | /// Returns whether the edge (i.e., this HalfEdge or its twin) is a boundary edge, i.e., adjacent to a hole. 24 | fn is_boundary(&self, mesh: &T::Mesh) -> bool; 25 | 26 | /// Returns the centroid of the edge. 27 | fn centroid(&self, mesh: &T::Mesh) -> T::Vec 28 | where 29 | T: EuclideanMeshType, 30 | { 31 | let v1 = self.origin(mesh).pos().clone(); 32 | let v2 = self.target(mesh).pos().clone(); 33 | (v1 + v2) * T::S::HALF 34 | } 35 | 36 | /// Iterates all (half)edges incident to the same face (counter-clockwise) 37 | fn edges_face<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator; 38 | 39 | /// Iterates all (half)edges incident to the same face (clockwise) 40 | fn edges_face_back<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator; 41 | 42 | /// Iterates all face ids incident to the edge 43 | /// (even for half-edges, this will return both faces if there are two 44 | /// or more than that if the edge is non-manifold) 45 | fn face_ids<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator; 46 | } 47 | 48 | #[cfg(test)] 49 | #[cfg(feature = "nalgebra")] 50 | mod tests { 51 | use itertools::Itertools; 52 | 53 | use crate::{extensions::nalgebra::*, prelude::*}; 54 | 55 | #[test] 56 | fn test_edge_basics_triangle() { 57 | let mesh = Mesh3d64::regular_polygon(1.0, 3); 58 | let edge = mesh.edge(0); 59 | assert_eq!(edge.origin(&mesh).id(), 0); 60 | assert_eq!(edge.target(&mesh).id(), 1); 61 | assert_eq!(edge.is_boundary(&mesh), true); 62 | assert_eq!(edge.payload().is_empty(), true); 63 | assert_eq!(edge.face_ids(&mesh).collect::>(), vec![0]); 64 | assert!(edge.edges_face(&mesh).count() == 3); 65 | assert!(edge.edges_face_back(&mesh).count() == 3); 66 | assert_eq!( 67 | edge.edges_face(&mesh) 68 | .map(|e| e.id()) 69 | .collect_vec() 70 | .iter() 71 | .rev() 72 | .collect_vec(), 73 | edge.edges_face_back(&mesh) 74 | .map(|e| e.id()) 75 | .collect_vec() 76 | .iter() 77 | .cycle() 78 | .skip(1) 79 | .take(3) 80 | .collect_vec() 81 | ); 82 | for edge in mesh.edges() { 83 | assert!(edge.is_boundary(&mesh)); 84 | assert_eq!(edge.face_ids(&mesh).count(), 1); 85 | assert!(edge.edges_face(&mesh).count() == 3); 86 | } 87 | } 88 | 89 | #[test] 90 | fn test_edge_basics_cube() { 91 | let mesh = Mesh3d64::cube(1.0); 92 | for edge in mesh.edges() { 93 | assert!(!edge.is_boundary(&mesh)); 94 | assert_eq!(edge.face_ids(&mesh).count(), 2); 95 | assert!(edge.edges_face(&mesh).count() == 4); 96 | assert!(edge.edges_face_back(&mesh).count() == 4); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mesh/edge/curved.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::{Scalar, Transformable, Vector}, 3 | mesh::{EdgeBasics, EuclideanMeshType, VertexBasics}, 4 | }; 5 | 6 | /// The type of curve that the edge represents. 7 | #[derive(Clone, Default, Copy, Debug, PartialEq, Hash)] 8 | pub enum CurvedEdgeType> { 9 | /// A linear edge 10 | #[default] 11 | Linear, 12 | /// A quadratic bezier edge 13 | QuadraticBezier(T::Vec), 14 | /// A cubic bezier edge 15 | CubicBezier(T::Vec, T::Vec), 16 | } 17 | 18 | impl> CurvedEdgeType { 19 | /// Returns the coordinates at a specific point on the curve 20 | /// The parameter `t` is in the range [0, 1] 21 | pub fn point_at(&self, edge: &T::Edge, mesh: &T::Mesh, t: T::S) -> T::Vec { 22 | let start: T::Vec = edge.origin(mesh).pos(); 23 | let end: T::Vec = edge.target(mesh).pos(); 24 | let res: T::Vec = match self { 25 | CurvedEdgeType::Linear => start.lerped(&end, t).clone(), 26 | CurvedEdgeType::QuadraticBezier(control_point) => { 27 | let tt = t * t; 28 | let s = T::S::ONE - t; 29 | let ss = s * s; 30 | start * ss + *control_point * T::S::TWO * s * t + end * tt 31 | } 32 | CurvedEdgeType::CubicBezier(control_point1, control_point2) => { 33 | let tt = t * t; 34 | let ttt = tt * t; 35 | let s = T::S::ONE - t; 36 | let ss = s * s; 37 | let sss = ss * s; 38 | start * sss 39 | + *control_point1 * T::S::THREE * ss * t 40 | + *control_point2 * T::S::THREE * s * tt 41 | + end * ttt 42 | } 43 | }; 44 | return res; 45 | } 46 | 47 | /// Returns if two curves are about equal (control point wise) within a certain epsilon 48 | pub fn is_about(&self, other: &Self, epsilon: T::S) -> bool { 49 | match (self, other) { 50 | (CurvedEdgeType::Linear, CurvedEdgeType::Linear) => true, 51 | (CurvedEdgeType::QuadraticBezier(c1), CurvedEdgeType::QuadraticBezier(c2)) => { 52 | c1.is_about(c2, epsilon) 53 | } 54 | (CurvedEdgeType::CubicBezier(c1, c2), CurvedEdgeType::CubicBezier(c3, c4)) => { 55 | c1.is_about(c3, epsilon) && c2.is_about(c4, epsilon) 56 | } 57 | _ => false, 58 | } 59 | } 60 | } 61 | 62 | /// Edge that can be a line or some type of curve. 63 | pub trait CurvedEdge>: EdgeBasics { 64 | /// Returns the curve type of the edge 65 | fn curve_type(&self) -> CurvedEdgeType; 66 | 67 | /// Overwrites the curve type of the edge 68 | fn set_curve_type(&mut self, curve_type: CurvedEdgeType); 69 | 70 | /// Converts the curved edge to a uniformly spaced sequence of `n` line segments 71 | fn flatten_uniform(&self, n: usize, mesh: &T::Mesh) -> Vec { 72 | assert!(n > 0); 73 | return (0..n - 1) 74 | .into_iter() 75 | .map(|i| { 76 | self.curve_type().point_at( 77 | self, 78 | mesh, 79 | T::S::from_usize(i + 1) / T::S::from_usize(n), 80 | ) 81 | }) 82 | .collect(); 83 | } 84 | 85 | /// Converts the curved edge to a sequence of line segments with a specific error using De Casteljau's algorithm 86 | fn flatten_casteljau(&self, error: T::S, mesh: &T::Mesh) -> Vec { 87 | fn recursive_flatten>( 88 | curve: &CurvedEdgeType, 89 | edge: &T::Edge, 90 | mesh: &T::Mesh, 91 | t0: T::S, 92 | t1: T::S, 93 | error: T::S, 94 | lines: &mut Vec, 95 | ) where 96 | T::Edge: CurvedEdge, 97 | { 98 | let p0 = curve.point_at(edge, mesh, t0); 99 | let p1 = curve.point_at(edge, mesh, t1); 100 | let tm = (t0 + t1) / T::S::TWO; 101 | let pm = curve.point_at(edge, mesh, tm); 102 | let p_line = p0.lerped(&p1, T::S::HALF); 103 | let deviation = pm.distance(&p_line); 104 | 105 | if deviation <= error { 106 | // The segment is acceptable; push p1 107 | lines.push(p1); 108 | } else { 109 | // Subdivide further 110 | recursive_flatten(curve, edge, mesh, tm, t1, error, lines); 111 | recursive_flatten(curve, edge, mesh, t0, tm, error, lines); 112 | } 113 | } 114 | 115 | let mut lines = Vec::new(); 116 | // Start by adding the target point 117 | let curve = self.curve_type(); 118 | recursive_flatten(&curve, self, mesh, T::S::ZERO, T::S::ONE, error, &mut lines); 119 | // Reverse the points to get them in the correct order 120 | lines.reverse(); 121 | lines.pop(); 122 | return lines; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/mesh/edge/halfedge.rs: -------------------------------------------------------------------------------- 1 | use super::EdgeBasics; 2 | use crate::mesh::MeshType; 3 | 4 | /// Basic halfedge traits. 5 | pub trait HalfEdge>: EdgeBasics { 6 | /// Creates a new half-edge 7 | fn new(next: T::E, twin: T::E, prev: T::E, origin: T::V, face: T::F, payload: T::EP) -> Self; 8 | 9 | /// Sets the face of the HalfEdge. Panics if the face is already set. 10 | fn set_face(&mut self, face: T::F); 11 | 12 | /// Deletes the face of the HalfEdge 13 | fn delete_face(&mut self); 14 | 15 | /// Sets the next half-edge incident to the same face (including the holes) 16 | fn set_next(&mut self, next: T::E); 17 | 18 | /// Sets the previous half-edge incident to the same face (including the holes) 19 | fn set_prev(&mut self, prev: T::E); 20 | 21 | /// Sets the twin half-edge 22 | fn set_twin(&mut self, twin: T::E); 23 | 24 | /// Sets the origin vertex of the half-edge 25 | fn set_origin(&mut self, origin: T::V); 26 | 27 | /// Returns the next half-edge incident to the same face or boundary 28 | fn next(&self, mesh: &T::Mesh) -> T::Edge; 29 | 30 | /// Returns the next id 31 | fn next_id(&self) -> T::E; 32 | 33 | /// Returns the other, opposite half-edge 34 | fn twin(&self, mesh: &T::Mesh) -> T::Edge; 35 | 36 | /// Returns the twin id 37 | fn twin_id(&self) -> T::E; 38 | 39 | /// Returns the previous half-edge incident to the same face or boundary 40 | fn prev(&self, mesh: &T::Mesh) -> T::Edge; 41 | 42 | /// Returns the prev id 43 | fn prev_id(&self) -> T::E; 44 | 45 | /// Returns the source vertex of the half-edge 46 | fn origin_id(&self) -> T::V; 47 | 48 | /// Returns the target vertex id of the half-edge. Reached via the next half-edge, not the twin. 49 | fn target_id(&self, mesh: &T::Mesh) -> T::V; 50 | 51 | /// Returns the face the half-edge is incident to 52 | fn face<'a>(&'a self, mesh: &'a T::Mesh) -> Option<&'a T::Face>; 53 | 54 | /// Returns the face id 55 | fn face_id(&self) -> T::F; 56 | 57 | /// Returns the other face (incident to the twin) 58 | fn other_face<'a>(&'a self, mesh: &'a T::Mesh) -> Option<&'a T::Face>; 59 | 60 | /// Returns whether the edge (i.e., this HalfEdge and not necessarily its twin) is a boundary edge 61 | fn is_boundary_self(&self) -> bool; 62 | 63 | /// Returns whether the edge can reach the vertex when searching counter-clockwise along the face 64 | fn same_face(&self, mesh: &T::Mesh, v: T::V) -> bool; 65 | 66 | /// Like `same_face` but searches clockwise 67 | fn same_face_back(&self, mesh: &T::Mesh, v: T::V) -> bool; 68 | 69 | /// Flips the direction of the edge and its twin. 70 | /// Updates the neighboring edges, vertices, and faces. 71 | fn flip(e: T::E, mesh: &mut T::Mesh); 72 | } 73 | 74 | #[cfg(test)] 75 | #[cfg(feature = "nalgebra")] 76 | mod tests { 77 | use super::*; 78 | use crate::{extensions::nalgebra::*, prelude::*}; 79 | 80 | #[test] 81 | fn test_halfedge_triangle() { 82 | let mut mesh = Mesh3d64::regular_polygon(1.0, 3); 83 | for edge in mesh.edges() { 84 | assert!(edge.is_boundary_self() ^ (edge.twin(&mesh).is_boundary_self())); 85 | if edge.is_boundary_self() { 86 | assert!(edge.other_face(&mesh).is_some()); 87 | } 88 | } 89 | 90 | let e0 = mesh.edges().find(|e| !e.is_boundary_self()).unwrap().id(); 91 | let f0 = mesh.edge(e0).face_id(); 92 | mesh.edge_mut(e0).delete_face(); 93 | let edge = mesh.edge(e0); 94 | assert!(edge.is_boundary_self()); 95 | 96 | mesh.edge_mut(e0).set_face(f0); 97 | let edge = mesh.edge(e0); 98 | assert!(!edge.is_boundary_self()); 99 | 100 | assert!(mesh.flipped().check().is_ok()); 101 | assert!(mesh 102 | .flipped() 103 | .flipped() 104 | .is_trivially_isomorphic_pos(&mesh, 1e-6) 105 | .eq()); 106 | } 107 | 108 | #[test] 109 | fn test_halfedge_cube() { 110 | let mesh = Mesh3d64::cube(1.0); 111 | for edge in mesh.edges() { 112 | assert!(!edge.is_boundary_self()); 113 | assert!(edge.other_face(&mesh).is_some()); 114 | } 115 | 116 | for face in mesh.faces() { 117 | face.edges(&mesh).for_each(|e1| { 118 | face.edges(&mesh).for_each(|e2| { 119 | assert!(e1.same_face(&mesh, e2.origin_id())); 120 | }); 121 | }); 122 | } 123 | 124 | let flipped = mesh.flipped(); 125 | assert!(flipped.check().is_ok()); 126 | assert!(flipped 127 | .flipped() 128 | .is_trivially_isomorphic_pos(&mesh, 1e-6) 129 | .eq()); 130 | assert!(flipped 131 | .is_isomorphic_by_pos::<_, 3, _, MeshType3d64PNU>(&mesh, 1e-6) 132 | .eq()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/mesh/edge/mod.rs: -------------------------------------------------------------------------------- 1 | mod basics; 2 | mod halfedge; 3 | mod payload; 4 | mod curved; 5 | 6 | pub use basics::*; 7 | pub use halfedge::*; 8 | pub use payload::*; 9 | pub use curved::*; 10 | 11 | use super::MeshType; 12 | 13 | /// A directed or undirected edge or halfedge in a mesh. 14 | /// 15 | /// More specifically: 16 | /// - In an undirected graph, there is exactly one edge id for each undirected edge. Instances of the edge should can be different, i.e., `target` and `origin` can be swapped. 17 | /// - In a directed graph, there is exactly one edge id for each directed edge. An undirected edge therefore is a pair of `Edge`s, each with their own id. 18 | /// - In a halfedge mesh, there is exactly one edge id for each halfedge. An undirected edge therefore is a pair of `Edge`s, each with their own id. 19 | pub trait Edge: EdgeBasics { 20 | /// Associated mesh type 21 | type T: MeshType; 22 | } 23 | -------------------------------------------------------------------------------- /src/mesh/edge/payload.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hash; 2 | 3 | use crate::{ 4 | math::Transformable, 5 | mesh::{EuclideanMeshType, MeshType}, 6 | }; 7 | 8 | use super::CurvedEdgeType; 9 | 10 | /// A trait that defines how the payload of an edge should behave. 11 | pub trait EdgePayload: Clone + std::fmt::Debug + PartialEq { 12 | /// Returns a new default instance without any meaningful data. 13 | fn allocate() -> Self; 14 | 15 | /// Returns true if the payload is empty. 16 | fn is_empty(&self) -> bool; 17 | } 18 | 19 | /// The default edge payload can be safely constructed with a default constructor. 20 | /// For example, when extruding, it is ok for all new edges to have the same default payload. 21 | pub trait DefaultEdgePayload: EdgePayload + Default {} 22 | 23 | /// An empty edge payload if you don't need any additional information. 24 | #[derive(Debug, Clone, Copy, PartialEq, Default, Hash)] 25 | pub struct EmptyEdgePayload { 26 | _phantom: std::marker::PhantomData, 27 | } 28 | 29 | impl EdgePayload for EmptyEdgePayload { 30 | fn allocate() -> Self { 31 | Self { 32 | _phantom: std::marker::PhantomData, 33 | } 34 | } 35 | fn is_empty(&self) -> bool { 36 | true 37 | } 38 | } 39 | 40 | impl DefaultEdgePayload for EmptyEdgePayload {} 41 | 42 | impl> Transformable for EmptyEdgePayload { 43 | type Rot = T::Rot; 44 | type S = T::S; 45 | type Trans = T::Trans; 46 | type Vec = T::Vec; 47 | 48 | fn transform(&mut self, _: &Self::Trans) -> &mut Self { 49 | self 50 | } 51 | 52 | fn lerp(&mut self, _: &Self, _: Self::S) -> &mut Self { 53 | self 54 | } 55 | } 56 | 57 | /// A curved edge payload with nothing else 58 | #[derive(Debug, Default, Clone, PartialEq)] 59 | pub struct CurvedEdgePayload> { 60 | curve: CurvedEdgeType, 61 | } 62 | 63 | impl> CurvedEdgePayload { 64 | /// Returns the curve type of the edge 65 | pub fn curve_type(&self) -> CurvedEdgeType { 66 | self.curve 67 | } 68 | 69 | /// Sets the curve type of the edge 70 | pub fn set_curve_type(&mut self, curve_type: CurvedEdgeType) { 71 | self.curve = curve_type; 72 | } 73 | } 74 | 75 | // TODO: somehow make sure the twin is never curved or has the same curve type 76 | // TODO: make sure edge payloads are printed even for winged edges 77 | impl> EdgePayload for CurvedEdgePayload { 78 | fn allocate() -> Self { 79 | Default::default() 80 | } 81 | fn is_empty(&self) -> bool { 82 | match self.curve { 83 | CurvedEdgeType::Linear => true, 84 | _ => false, 85 | } 86 | } 87 | } 88 | 89 | impl> DefaultEdgePayload for CurvedEdgePayload {} 90 | 91 | impl> Transformable for CurvedEdgePayload { 92 | type Rot = T::Rot; 93 | type S = T::S; 94 | type Trans = T::Trans; 95 | type Vec = T::Vec; 96 | 97 | fn transform(&mut self, t: &Self::Trans) -> &mut Self { 98 | match &mut self.curve { 99 | CurvedEdgeType::Linear => {} 100 | CurvedEdgeType::QuadraticBezier(control_point) => { 101 | control_point.transform(t); 102 | } 103 | CurvedEdgeType::CubicBezier(control_point1, control_point2) => { 104 | control_point1.transform(t); 105 | control_point2.transform(t); 106 | } 107 | } 108 | self 109 | } 110 | 111 | fn lerp(&mut self, _other: &Self, _t: Self::S) -> &mut Self { 112 | match &mut self.curve { 113 | CurvedEdgeType::Linear => {} 114 | CurvedEdgeType::QuadraticBezier(_cp) => { 115 | todo!(); 116 | } 117 | CurvedEdgeType::CubicBezier(_cp1, _cp2) => { 118 | todo!(); 119 | } 120 | } 121 | self 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/mesh/face/basics.rs: -------------------------------------------------------------------------------- 1 | use crate::mesh::{EdgeBasics, VertexBasics}; 2 | 3 | use super::MeshType; 4 | 5 | /// A face in a mesh. 6 | /// 7 | /// Isn't necessarily planar or triangular. 8 | pub trait FaceBasics>: std::fmt::Debug + Clone + Copy { 9 | /// Returns the index of the face. 10 | fn id(&self) -> T::F; 11 | 12 | /// Returns an edge incident to the face. 13 | fn edge(&self, mesh: &T::Mesh) -> T::Edge; 14 | 15 | /// Sets the representative edge incident to the face. 16 | fn set_edge(&mut self, edge: T::E); 17 | 18 | /// Returns the id of a half-edge incident to the face. 19 | fn edge_id(&self) -> T::E; 20 | 21 | /// Whether the face is allowed to be curved. 22 | fn may_be_curved(&self) -> bool; 23 | 24 | /// Get the number of edges of the face. 25 | fn num_edges(&self, mesh: &T::Mesh) -> usize; 26 | 27 | /// Get the number of vertices of the face. 28 | fn num_vertices(&self, mesh: &T::Mesh) -> usize; 29 | 30 | /// Get the number of triangles of the face. (n-2)*3 31 | fn num_triangles(&self, mesh: &T::Mesh) -> usize; 32 | 33 | /// Returns the face payload. 34 | fn payload(&self) -> &T::FP; 35 | 36 | /// Returns a mutable reference to the face payload. 37 | fn payload_mut(&mut self) -> &mut T::FP; 38 | 39 | /// Iterates all vertices adjacent to the face 40 | fn vertices<'a>( 41 | &'a self, 42 | mesh: &'a T::Mesh, 43 | ) -> impl Iterator + 'a + Clone + ExactSizeIterator; 44 | 45 | /// Iterates all vertex ids adjacent to the face 46 | fn vertex_ids<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a 47 | where 48 | T: 'a, 49 | { 50 | self.vertices(mesh).map(|v| v.id()) 51 | } 52 | 53 | /// Whether the face has holes. 54 | /// The data structure (currently!) cannot represent holes, so this is always false. 55 | fn has_holes(&self) -> bool { 56 | return false; 57 | } 58 | 59 | /// Iterates all half-edges incident to the face 60 | fn edges<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator; 61 | 62 | /// Iterates all half-edge ids incident to the face 63 | fn edge_ids<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a 64 | where 65 | T: 'a, 66 | { 67 | self.edges(mesh).map(|e| e.id()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/mesh/face/mod.rs: -------------------------------------------------------------------------------- 1 | //mod geometry; 2 | mod basics; 3 | mod face3d; 4 | mod payload; 5 | 6 | pub use basics::*; 7 | pub use face3d::*; 8 | pub use payload::*; 9 | 10 | use super::{EuclideanMeshType, MeshType, VertexBasics}; 11 | use crate::math::VectorIteratorExt; 12 | 13 | // TODO: Remove methods in trait Face 14 | 15 | /// A face in a mesh. 16 | /// 17 | /// Isn't necessarily planar or triangular. 18 | pub trait Face: FaceBasics { 19 | /// Associated mesh type 20 | type T: MeshType; 21 | 22 | /// Naive method to get the center of the face by averaging the vertices. 23 | fn centroid( 24 | &self, 25 | mesh: &::Mesh, 26 | ) -> >::Vec 27 | where 28 | Self::T: EuclideanMeshType, 29 | { 30 | self.vertices(mesh).map(|v| v.pos()).stable_mean() 31 | } 32 | 33 | /// Whether a triangle shares a halfedge with the face. 34 | /// 35 | /// If there is no evidence that the triangle is touching the face, return None. 36 | /// Given that all vertices are part of this face, this implies that the triangle is part of the face. 37 | fn triangle_touches_boundary( 38 | &self, 39 | mesh: &::Mesh, 40 | v0: ::V, 41 | v1: ::V, 42 | v2: ::V, 43 | ) -> Option; 44 | } 45 | -------------------------------------------------------------------------------- /src/mesh/face/payload.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::Transformable, 3 | mesh::{EuclideanMeshType, MeshType}, 4 | }; 5 | 6 | /// A trait that defines what data you can store in a face. 7 | pub trait FacePayload: Clone + Copy + PartialEq + Eq + std::fmt::Debug { 8 | /// Returns a new default instance without any meaningful data. 9 | fn allocate() -> Self; 10 | } 11 | 12 | /// A FacePayload that is safe to be constructed with defaults. 13 | /// For example, when extruding, it is ok for all new faces to have the same default payload. 14 | pub trait DefaultFacePayload: FacePayload + Default {} 15 | 16 | /// An empty face payload if you don't need any additional information. 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 18 | pub struct EmptyFacePayload { 19 | _phantom: std::marker::PhantomData, 20 | } 21 | 22 | impl FacePayload for EmptyFacePayload { 23 | fn allocate() -> Self { 24 | Self { 25 | _phantom: std::marker::PhantomData, 26 | } 27 | } 28 | } 29 | 30 | impl DefaultFacePayload for EmptyFacePayload {} 31 | 32 | impl> Transformable for EmptyFacePayload { 33 | type Rot = T::Rot; 34 | type S = T::S; 35 | type Trans = T::Trans; 36 | type Vec = T::Vec; 37 | 38 | fn transform(&mut self, _: &T::Trans) -> &mut Self { 39 | self 40 | } 41 | 42 | fn lerp(&mut self, _: &Self, _: Self::S) -> &mut Self { 43 | self 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mesh/mesh/check.rs: -------------------------------------------------------------------------------- 1 | use super::{MeshBasics, MeshType}; 2 | 3 | /// A trait for checking the consistency of a mesh. 4 | pub trait MeshChecker>: MeshBasics { 5 | /// Checks the mesh for consistency 6 | fn check(&self) -> Result<(), String>; 7 | } 8 | -------------------------------------------------------------------------------- /src/mesh/mesh/halfedge.rs: -------------------------------------------------------------------------------- 1 | use crate::mesh::{EdgeBasics, HalfEdge}; 2 | 3 | use super::{MeshBasics, MeshType}; 4 | 5 | /// Some basic operations to retrieve information about the mesh. 6 | pub trait HalfEdgeMesh>: MeshBasics 7 | where 8 | T::Edge: HalfEdge, 9 | { 10 | /// Returns an iterator over all non-deleted halfedge pairs without duplicates 11 | fn twin_edges<'a>(&'a self) -> impl Iterator 12 | where 13 | T::Edge: 'a, 14 | T: 'a, 15 | { 16 | self.edges().filter_map(move |e| { 17 | if e.twin_id() < e.id() { 18 | None 19 | } else { 20 | Some((e, self.edge(e.twin_id()))) 21 | } 22 | }) 23 | } 24 | 25 | /// Iterates forwards over the half-edge chain starting at the given edge 26 | fn edges_from<'a>(&'a self, e: T::E) -> impl Iterator; 27 | 28 | /// Iterates backwards over the half-edge chain starting at the given edge 29 | fn edges_back_from<'a>(&'a self, e: T::E) -> impl Iterator; 30 | 31 | /// Flips the edge, i.e., swaps the origin and target vertices. 32 | fn flip_edge(&mut self, e: T::E) -> &mut Self { 33 | HalfEdge::::flip(e, self); 34 | self 35 | } 36 | 37 | /// Returns a flipped clone of the mesh. 38 | fn flipped(&self) -> Self { 39 | let mut mesh = self.clone(); 40 | mesh.flip(); 41 | mesh 42 | } 43 | 44 | /// Flip all edges. The mesh won't change its topology, but the indices of all edges and their payloads will be swapped. 45 | fn flip(&mut self) -> &mut Self { 46 | // PERF: this is an unnecessary clone 47 | let ids: Vec = self.twin_edges().map(|(e, _)| e.id()).collect(); 48 | ids.iter().for_each(|&e| { 49 | self.flip_edge(e); 50 | }); 51 | self 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/mesh/mesh/mesh_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::{ 4 | math::{ 5 | HasPosition, IndexType, Polygon, Rotator, Scalar, TransformTrait, Transformable, Vector, 6 | Vector2D, Vector3D, 7 | }, 8 | mesh::{ 9 | Edge, EdgePayload, Face, Face3d, FacePayload, HalfEdge, HalfEdgeMesh, HalfEdgeVertex, 10 | MeshBuilder, MeshHalfEdgeBuilder, MeshPayload, MeshTrait, Vertex, VertexPayload, 11 | }, 12 | }; 13 | 14 | use super::MeshPosition; 15 | 16 | // TODO: The `Copy` here is weird. Should probably remove it. 17 | // TODO: The Vec / Rot / S parts shouldn't be part of the default MeshType but only for HasPosition or something like that 18 | 19 | /// This trait defines all the associated types used in a mesh and puts them into relation. 20 | pub trait MeshType: Copy + Default + Debug + Eq { 21 | /// The type of the index used for edge. 22 | type E: IndexType; 23 | 24 | /// The type of the index used for vertices. 25 | type V: IndexType; 26 | 27 | /// The type of the index used for faces. 28 | type F: IndexType; 29 | 30 | /// The type of the edge payload. 31 | type EP: EdgePayload; 32 | 33 | /// The type of the vertex payload. 34 | type VP: VertexPayload; 35 | 36 | /// The type of the face payload. 37 | type FP: FacePayload; 38 | 39 | /// The type of the mesh payload. 40 | type MP: MeshPayload; 41 | 42 | /// The type of the mesh. 43 | type Mesh: MeshTrait + MeshBuilder; 44 | 45 | /// The type of the (half-)edge or arc. 46 | type Edge: Edge; 47 | 48 | /// The type of the vertex. 49 | type Vertex: Vertex; 50 | 51 | /// The type of the face. 52 | type Face: Face; 53 | } 54 | 55 | /// Extends the `MeshType` trait to meshes in euclidean space. 56 | pub trait EuclideanMeshType: 57 | MeshType< 58 | Mesh: MeshPosition, 59 | VP: Transformable 60 | + HasPosition, 61 | > 62 | { 63 | /// The type of the vector used for vertices. 64 | type Vec: Vector 65 | + Transformable; 66 | 67 | /// The 2d vector type derived from the default vector 68 | type Vec2: Vector2D; 69 | 70 | /// The type of the scalar used for vertex position. 71 | type S: Scalar; 72 | 73 | /// The type of the transformation used for vertices. 74 | type Trans: TransformTrait; 75 | 76 | /// The type of the rotation data used for vertices. 77 | type Rot: Rotator; 78 | 79 | /// The implementation of 2d polygons. 80 | type Poly: Polygon; 81 | } 82 | 83 | /// A `MeshType` specialized for half-edge meshes 84 | pub trait MeshTypeHalfEdge: 85 | MeshType< 86 | Mesh: MeshBuilder + HalfEdgeMesh + MeshHalfEdgeBuilder, 87 | Edge: HalfEdge, 88 | Vertex: HalfEdgeVertex, 89 | > 90 | { 91 | } 92 | 93 | /// A `MeshType` specialized for meshes with 3d position data 94 | pub trait MeshType3D: EuclideanMeshType<3, Vec: Vector3D, Face: Face3d> {} 95 | -------------------------------------------------------------------------------- /src/mesh/mesh/mod.rs: -------------------------------------------------------------------------------- 1 | //mod check; 2 | mod basics; 3 | mod builder; 4 | mod check; 5 | mod halfedge; 6 | mod iso; 7 | mod mesh_type; 8 | mod normals; 9 | mod path_builder; 10 | mod payload; 11 | mod position; 12 | mod topology; 13 | mod transform; 14 | mod triangulate; 15 | 16 | pub use basics::*; 17 | pub use builder::*; 18 | pub use check::*; 19 | pub use halfedge::*; 20 | pub use iso::*; 21 | pub use mesh_type::*; 22 | pub use normals::*; 23 | pub use path_builder::*; 24 | pub use payload::*; 25 | pub use position::*; 26 | pub use topology::*; 27 | pub use transform::*; 28 | pub use triangulate::*; 29 | 30 | #[cfg(feature = "netsci")] 31 | mod netsci; 32 | 33 | #[cfg(feature = "netsci")] 34 | pub use netsci::*; 35 | 36 | #[cfg(feature = "fonts")] 37 | mod fonts; 38 | 39 | #[cfg(feature = "fonts")] 40 | pub use fonts::*; 41 | 42 | /// The `MeshTrait` doesn't assume any specific data structure or topology, 43 | /// i.e., could be a manifold half-edge mesh, a topological directed graph, etc. 44 | pub trait MeshTrait: MeshBasics { 45 | /// Associated mesh type 46 | type T: MeshType; 47 | } 48 | -------------------------------------------------------------------------------- /src/mesh/mesh/normals.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::{HasNormal, Scalar, Vector, VectorIteratorExt}, 3 | mesh::{EuclideanMeshType, Face3d, FaceBasics, MeshBasics, MeshType3D, VertexBasics}, 4 | }; 5 | use std::collections::HashMap; 6 | 7 | /// The algorithm to use for generating normals. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 9 | pub enum GenerateNormals { 10 | /// Do not generate normals. (no vertex duplication) 11 | None, 12 | 13 | /// Generate flat normals per face. (full vertex duplication) 14 | #[default] 15 | Flat, 16 | 17 | /// Generate only smooth normals. (no vertex duplication) 18 | Smooth, 19 | // Use face groups to decide whether to generate flat or smooth normals. 20 | //Groups, 21 | } 22 | 23 | /// Methods to work with normals in a mesh. 24 | /// 25 | /// Normals can use different vector and scalar types than positions. But usually it's sensible to use the same types. 26 | pub trait WithNormals< 27 | const D: usize, 28 | VecN: Vector, 29 | SN: Scalar, 30 | T: EuclideanMeshType, 31 | >: MeshBasics where 32 | T::VP: HasNormal, 33 | { 34 | /// Generates flat normals and safes them in the mesh. 35 | /// Requires all vertices in the mesh to be duplicated. 36 | /// TODO: Implement this function and also the duplication methods. 37 | fn generate_flat_normals(&mut self) -> &mut Self { 38 | todo!("generate_normals_flat is not implemented yet"); 39 | } 40 | 41 | /// Generates smooth normals and safes them in the mesh. 42 | fn generate_smooth_normals(&mut self) -> &mut Self 43 | where 44 | T: MeshType3D, 45 | T::VP: HasNormal<3, >::Vec, S = >::S>, 46 | { 47 | // Smooth normals are calculated without vertex duplication. 48 | // Hence, we have to set the normals of the whole mesh. 49 | // we copy the vertices still to both compact the indices and set the normals without mutating the mesh 50 | let face_normals: HashMap = MeshBasics::faces(self) 51 | .map(|f| (f.id(), Face3d::normal(f, self).normalize())) 52 | .collect(); 53 | 54 | let normals = MeshBasics::vertices(self) 55 | .map(|v| { 56 | v.faces(self) 57 | .map(|f| face_normals[&f.id()]) 58 | .stable_mean() 59 | .normalize() 60 | }) 61 | .collect::>(); 62 | 63 | self.vertices_mut().enumerate().for_each(|(i, v)| { 64 | // set the average of face normals for each vertex 65 | v.payload_mut().set_normal(normals[i]); 66 | }); 67 | 68 | self 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/mesh/mesh/payload.rs: -------------------------------------------------------------------------------- 1 | use super::{EuclideanMeshType, MeshType}; 2 | use crate::math::Transformable; 3 | 4 | /// A trait representing a payload for a mesh. 5 | /// 6 | /// This could be used to associate the mesh with additional global data, 7 | /// such as a spatial data structure for finding vertices etc. 8 | pub trait MeshPayload: Default + Clone + PartialEq + std::fmt::Debug {} 9 | 10 | /// An empty mesh payload that can be used when no additional data is needed. 11 | #[derive(Debug, Clone, Default, PartialEq)] 12 | pub struct EmptyMeshPayload { 13 | _phantom: std::marker::PhantomData, 14 | } 15 | 16 | impl MeshPayload for EmptyMeshPayload {} 17 | 18 | impl std::fmt::Display for EmptyMeshPayload { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | write!(f, "EmptyMeshPayload") 21 | } 22 | } 23 | 24 | impl> Transformable for EmptyMeshPayload { 25 | type Rot = T::Rot; 26 | type S = T::S; 27 | type Trans = T::Trans; 28 | type Vec = T::Vec; 29 | 30 | fn transform(&mut self, _: &Self::Trans) -> &mut Self { 31 | self 32 | } 33 | 34 | fn lerp(&mut self, _: &Self, _: Self::S) -> &mut Self { 35 | self 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/mesh/mesh/position.rs: -------------------------------------------------------------------------------- 1 | use super::{basics::MeshBasics, EuclideanMeshType}; 2 | use crate::{math::VectorIteratorExt, mesh::VertexBasics}; 3 | 4 | /// Methods for transforming meshes. 5 | pub trait MeshPosition>: 6 | MeshBasics 7 | { 8 | /// Returns the mean of all vertex positions. 9 | fn centroid(&self) -> T::Vec { 10 | self.vertices().map(|v| v.pos()).stable_mean() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/mesh/mesh/topology.rs: -------------------------------------------------------------------------------- 1 | use crate::mesh::VertexBasics; 2 | 3 | use super::{MeshBasics, MeshType}; 4 | use std::collections::{HashMap, HashSet, VecDeque}; 5 | 6 | /// Methods concerned with mesh topology. 7 | pub trait MeshTopology>: MeshBasics { 8 | /// Returns the shortest path (least number of edges) between two vertices 9 | /// or returns `None` if no path exists. 10 | /// Uses Breadth-First Search (BFS) to find the shortest path. 11 | fn shortest_path(&self, v0: T::V, v1: T::V) -> Option> { 12 | // TODO: This could easily work without halfedge but any graph 13 | 14 | if v0 == v1 { 15 | return Some(vec![v0]); 16 | } 17 | 18 | let mut queue = VecDeque::with_capacity(self.num_vertices()); 19 | let mut visited = HashSet::with_capacity(self.num_vertices()); 20 | let mut predecessor = HashMap::with_capacity(self.num_vertices()); 21 | 22 | queue.push_back(v1); 23 | visited.insert(v1); 24 | predecessor.insert(v1, None); 25 | 26 | while let Some(current) = queue.pop_front() { 27 | for neighbor in self.vertex(current).neighbor_ids(self) { 28 | if visited.contains(&neighbor) { 29 | continue; 30 | } 31 | visited.insert(neighbor); 32 | predecessor.insert(neighbor, Some(current)); 33 | queue.push_back(neighbor); 34 | 35 | if neighbor == v0 { 36 | let mut path = Vec::new(); 37 | let mut step = Some(v0); 38 | while let Some(vertex) = step { 39 | path.push(vertex); 40 | step = predecessor[&vertex]; 41 | } 42 | return Some(path); 43 | } 44 | } 45 | } 46 | 47 | None 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/mesh/mesh/transform.rs: -------------------------------------------------------------------------------- 1 | use super::{basics::MeshBasics, EuclideanMeshType}; 2 | use crate::{ 3 | math::Transformable, 4 | mesh::{EdgeBasics, FaceBasics, VertexBasics}, 5 | }; 6 | 7 | /// Methods for transforming meshes. 8 | pub trait TransformableMesh>: 9 | MeshBasics 10 | where 11 | T::VP: Transformable, 12 | T::EP: Transformable, 13 | T::FP: Transformable, 14 | T::MP: Transformable, 15 | { 16 | /// Transforms all vertices in the mesh 17 | fn transform(&mut self, t: &T::Trans) -> &mut Self { 18 | for v in self.vertices_mut() { 19 | v.payload_mut().transform(t); 20 | } 21 | for e in self.edges_mut() { 22 | e.payload_mut().transform(t); 23 | } 24 | for f in self.faces_mut() { 25 | f.payload_mut().transform(t); 26 | } 27 | self.payload_mut().transform(t); 28 | self 29 | } 30 | 31 | /// Returns a transformed clone of the mesh 32 | fn transformed(&self, t: &T::Trans) -> Self { 33 | let mut mesh = self.clone(); 34 | mesh.transform(t); 35 | mesh 36 | } 37 | 38 | /// Translates all vertices in the mesh 39 | fn translate(&mut self, t: &T::Vec) -> &mut Self { 40 | for v in self.vertices_mut() { 41 | v.payload_mut().translate(t); 42 | } 43 | for e in self.edges_mut() { 44 | e.payload_mut().translate(t); 45 | } 46 | for f in self.faces_mut() { 47 | f.payload_mut().translate(t); 48 | } 49 | self.payload_mut().translate(t); 50 | self 51 | } 52 | 53 | /// Returns a translated clone of the mesh 54 | fn translated(&self, t: &T::Vec) -> Self { 55 | let mut mesh = self.clone(); 56 | mesh.translate(t); 57 | mesh 58 | } 59 | 60 | /// Rotates all vertices in the mesh 61 | fn rotate(&mut self, rotation: &T::Rot) -> &mut Self { 62 | for v in self.vertices_mut() { 63 | v.payload_mut().rotate(rotation); 64 | } 65 | for e in self.edges_mut() { 66 | e.payload_mut().rotate(rotation); 67 | } 68 | for f in self.faces_mut() { 69 | f.payload_mut().rotate(rotation); 70 | } 71 | self.payload_mut().rotate(rotation); 72 | self 73 | } 74 | 75 | /// Returns a rotated clone of the mesh 76 | fn rotated(&self, rotation: &T::Rot) -> Self { 77 | let mut mesh = self.clone(); 78 | mesh.rotate(rotation); 79 | mesh 80 | } 81 | 82 | /// Scales all vertices in the mesh 83 | fn scale(&mut self, scale: &T::Vec) -> &mut Self { 84 | for v in self.vertices_mut() { 85 | v.payload_mut().scale(scale); 86 | } 87 | for e in self.edges_mut() { 88 | e.payload_mut().scale(scale); 89 | } 90 | for f in self.faces_mut() { 91 | f.payload_mut().scale(scale); 92 | } 93 | self.payload_mut().scale(scale); 94 | self 95 | } 96 | 97 | /// Returns a scaled clone of the mesh 98 | fn scaled(&self, scale: &T::Vec) -> Self { 99 | let mut mesh = self.clone(); 100 | mesh.scale(scale); 101 | mesh 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/mesh/mesh/triangulate.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::{basics::MeshBasics, MeshType, MeshType3D}; 4 | use crate::{ 5 | math::{HasNormal, IndexType, Vector}, 6 | mesh::{Face3d, FaceBasics, Triangulation, VertexBasics}, 7 | tesselate::{triangulate_face, TesselationMeta, TriangulationAlgorithm}, 8 | }; 9 | 10 | /// Methods for transforming meshes. 11 | pub trait Triangulateable>: MeshBasics { 12 | /// convert the mesh to triangles and get all indices to do so. 13 | /// Compact the vertices and return the indices 14 | fn triangulate( 15 | &self, 16 | algorithm: TriangulationAlgorithm, 17 | meta: &mut TesselationMeta, 18 | ) -> (Vec, Vec) 19 | where 20 | T: MeshType3D, 21 | { 22 | let mut indices = Vec::new(); 23 | for f in self.faces() { 24 | let mut tri = Triangulation::new(&mut indices); 25 | triangulate_face::(f, self, &mut tri, algorithm, meta) 26 | 27 | // TODO debug_assert!(tri.verify_full()); 28 | } 29 | 30 | let vs = self.dense_vertices(&mut indices); 31 | (indices, vs) 32 | } 33 | 34 | /// Triangulates the mesh and duplicates the vertices for use with flat normals. 35 | /// This doesn't duplicate the halfedge mesh but only the exported vertex buffer. 36 | fn triangulate_and_generate_flat_normals_post( 37 | &self, 38 | algorithm: TriangulationAlgorithm, 39 | meta: &mut TesselationMeta, 40 | ) -> (Vec, Vec) 41 | where 42 | T: MeshType3D, 43 | T::VP: HasNormal<3, T::Vec, S = T::S>, 44 | { 45 | let mut vertices = Vec::new(); 46 | let mut indices = Vec::new(); 47 | 48 | for f in self.faces() { 49 | let mut tri = Triangulation::new(&mut indices); 50 | let face_normal = Face3d::normal(f, self).normalize(); 51 | let mut id_map = HashMap::new(); 52 | // generate a new list of vertices (full duplication) 53 | f.vertices(self).for_each(|v| { 54 | let mut p = v.payload().clone(); 55 | id_map.insert(v.id(), IndexType::new(vertices.len())); 56 | p.set_normal(face_normal); 57 | vertices.push(p) 58 | }); 59 | triangulate_face::(f, self, &mut tri, algorithm, meta); 60 | tri.map_indices(&id_map); 61 | } 62 | 63 | (indices, vertices) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/mesh/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the representation of the mesh. It defines the vertex, edge, face and mesh structs. 2 | 3 | mod edge; 4 | mod face; 5 | mod mesh; 6 | mod triangulation; 7 | mod vertex; 8 | 9 | pub use edge::*; 10 | pub use face::*; 11 | pub use mesh::*; 12 | pub use triangulation::*; 13 | pub use vertex::*; 14 | -------------------------------------------------------------------------------- /src/mesh/vertex/basics.rs: -------------------------------------------------------------------------------- 1 | // TODO: iterator for neighboring faces 2 | 3 | use crate::{ 4 | math::{HasPosition, Scalar, Vector}, 5 | mesh::MeshType, 6 | }; 7 | 8 | /// Basic vertex functionality for a mesh 9 | pub trait VertexBasics: std::fmt::Debug + Clone { 10 | /// Returns the index of the vertex 11 | fn id(&self) -> T::V; 12 | 13 | /// Returns the payload of the vertex 14 | fn payload(&self) -> &T::VP; 15 | 16 | /// Returns the vertex coordinates of the payload 17 | fn pos>(&self) -> Vec 18 | where 19 | T::VP: HasPosition, 20 | { 21 | *self.payload().pos() 22 | } 23 | 24 | /// Returns a mutable reference to the payload of the vertex 25 | fn payload_mut(&mut self) -> &mut T::VP; 26 | 27 | /// Returns an outgoing edge incident to the vertex 28 | fn edge_id(&self, mesh: &T::Mesh) -> T::E; 29 | 30 | /// Returns an outgoing edge incident to the vertex 31 | fn edge(&self, mesh: &T::Mesh) -> Option; 32 | 33 | /// Returns whether the vertex is a boundary vertex 34 | fn is_boundary(&self, mesh: &T::Mesh) -> bool; 35 | 36 | /// Returns whether the vertex has only one edge incident to it 37 | fn has_only_one_edge(&self, mesh: &T::Mesh) -> bool; 38 | 39 | /// Iterates all vertices adjacent to the vertex in the same manifold edge wheel (clockwise) 40 | fn vertices<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a; 41 | 42 | /// Iterates all faces adjacent to this vertex in the same manifold edge wheel (clockwise) 43 | fn faces<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a 44 | where 45 | T: 'a; 46 | 47 | /// Iterates the ids of all neighbors of the vertex 48 | fn neighbor_ids<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a { 49 | self.vertices(mesh).map(|v| v.id()) 50 | } 51 | 52 | /// Returns the degree of the vertex 53 | fn degree(&self, mesh: &T::Mesh) -> usize { 54 | self.edges_out(mesh).count() 55 | } 56 | 57 | /// Iterates all outgoing (half)edges (resp. all edges in outwards-direction 58 | /// if undirected) incident to this vertex (clockwise) 59 | fn edges_out<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a; 60 | 61 | /// Iterates all ingoing (half)edges (resp. all edges in inwards-direction 62 | /// if undirected) incident to this vertex (clockwise) 63 | fn edges_in<'a>(&'a self, mesh: &'a T::Mesh) -> impl Iterator + 'a; 64 | 65 | /* 66 | /// Iterates the wheel of vertices (will have length one if the vertex is manifold) 67 | #[inline(always)] 68 | pub fn wheel<'a>( 69 | &'a self, 70 | mesh: &'a T::Mesh, 71 | ) -> impl Iterator> + 'a { 72 | NonmanifoldVertexIterator::new(self.clone(), mesh) 73 | }*/ 74 | } 75 | -------------------------------------------------------------------------------- /src/mesh/vertex/halfedge.rs: -------------------------------------------------------------------------------- 1 | use super::VertexBasics; 2 | use crate::mesh::{EdgeBasics, HalfEdge, MeshType}; 3 | use itertools::Itertools; 4 | 5 | /// Basic vertex functionality for a mesh 6 | pub trait HalfEdgeVertex: VertexBasics 7 | where 8 | T::Edge: HalfEdge, 9 | { 10 | /// Changes the representative of the outgoing edges 11 | fn set_edge(&mut self, edge: T::E); 12 | 13 | /// Returns an outgoing boundary edge incident to the vertex 14 | fn outgoing_boundary_edge(&self, mesh: &T::Mesh) -> Option { 15 | // TODO: Assumes a manifold vertex. Otherwise, there can be multiple boundary edges! 16 | debug_assert!( 17 | self.edges_out(mesh).count() == 0 18 | || self 19 | .edges_out(mesh) 20 | .filter(|e| e.is_boundary_self()) 21 | .exactly_one() 22 | .is_ok(), 23 | "Vertex {} is not manifold, edges are: {:?}", 24 | self.id(), 25 | self.edges_out(mesh).map(|e| e.id()).collect_vec() 26 | ); 27 | 28 | self.edges_out(mesh).find_map(|e| { 29 | if e.is_boundary_self() { 30 | Some(e.id()) 31 | } else { 32 | None 33 | } 34 | }) 35 | } 36 | 37 | /// Returns an ingoing boundary edge incident to the vertex 38 | fn ingoing_boundary_edge(&self, mesh: &T::Mesh) -> Option { 39 | debug_assert!( 40 | self.edges_in(mesh) 41 | .filter(|e| e.is_boundary_self()) 42 | .exactly_one() 43 | .is_ok(), 44 | "Vertex {} is not manifold", 45 | self.id() 46 | ); 47 | 48 | self.edges_in(mesh).find_map(|e| { 49 | if e.is_boundary_self() { 50 | Some(e.id()) 51 | } else { 52 | None 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/mesh/vertex/interpolator.rs: -------------------------------------------------------------------------------- 1 | use super::{HasPosition, VertexBasics}; 2 | use crate::{ 3 | math::{Scalar, Vector, Vector3D}, 4 | mesh::{EuclideanMeshType, MeshBasics, MeshType}, 5 | }; 6 | 7 | /// A trait that defines how vertex interpolators should behave. 8 | /// They interpolate vertices resp. their positions. 9 | pub trait VertexInterpolator { 10 | /// Interpolates the vertex positions based on the given vertices. 11 | fn call(&self, mesh: &T::Mesh, vertices: [(usize, T::V); N]) -> T::VP; 12 | } 13 | 14 | /// Vertex interpolator that performs linear interpolation. 15 | pub struct LinearVertexInterpolator {} 16 | 17 | impl> VertexInterpolator<3, T> 18 | for LinearVertexInterpolator 19 | where 20 | T::VP: HasPosition, 21 | { 22 | /// Subdivides by linear interpolation of the positions of the vertices. 23 | fn call(&self, mesh: &T::Mesh, [(i, vi), (j, vj), (k, vk)]: [(usize, T::V); 3]) -> T::VP { 24 | let pi = mesh.vertex(vi).pos(); 25 | let pj = mesh.vertex(vj).pos(); 26 | let pk = mesh.vertex(vk).pos(); 27 | T::VP::from_pos( 28 | (pi * T::S::from_usize(i) + pj * T::S::from_usize(j) + pk * T::S::from_usize(k)) 29 | / T::S::from_usize(i + j + k), 30 | ) 31 | } 32 | } 33 | 34 | /// Vertex interpolator that performs spherical linear interpolation (slerp). 35 | pub struct SlerpVertexInterpolator> { 36 | center: T::Vec, 37 | radius: T::S, 38 | } 39 | 40 | impl> SlerpVertexInterpolator { 41 | /// Creates a new SlerpVertexInterpolator with the given center and radius. 42 | /// Theoretically the radius could be inferred from the vertices, but to 43 | /// enforce a consistent radius and improve numerical stability, you should 44 | /// pass it explicitly. 45 | pub fn new(center: T::Vec, radius: T::S) -> Self { 46 | Self { center, radius } 47 | } 48 | } 49 | 50 | impl> VertexInterpolator<3, T> 51 | for SlerpVertexInterpolator 52 | where 53 | T::Vec: Vector3D, 54 | { 55 | /// Subdivides by linear interpolation of the positions of the vertices. 56 | fn call(&self, mesh: &T::Mesh, [(i, vi), (j, vj), (k, vk)]: [(usize, T::V); 3]) -> T::VP { 57 | let pi = (mesh.vertex(vi).pos() - self.center).normalize(); 58 | let pj = (mesh.vertex(vj).pos() - self.center).normalize(); 59 | let pk = (mesh.vertex(vk).pos() - self.center).normalize(); 60 | 61 | // slerp 62 | let pos = if i == 0 { 63 | pj.slerp(&pk, T::S::HALF) 64 | } else if j == 0 { 65 | pk.slerp(&pi, T::S::HALF) 66 | } else if k == 0 { 67 | pi.slerp(&pj, T::S::HALF) 68 | } else { 69 | todo!("slerp 3") 70 | }; 71 | 72 | T::VP::from_pos(self.center + pos.normalize() * self.radius) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/mesh/vertex/mod.rs: -------------------------------------------------------------------------------- 1 | mod basics; 2 | mod halfedge; 3 | mod interpolator; 4 | mod payload; 5 | 6 | pub use basics::*; 7 | pub use halfedge::*; 8 | pub use interpolator::*; 9 | pub use payload::*; 10 | 11 | use super::MeshType; 12 | use crate::math::HasPosition; 13 | 14 | /// A vertex in a mesh. 15 | pub trait Vertex: VertexBasics { 16 | /// Associated mesh type 17 | type T: MeshType; 18 | } 19 | -------------------------------------------------------------------------------- /src/mesh/vertex/payload.rs: -------------------------------------------------------------------------------- 1 | //! Payloads for vertices in n-dimensional space. 2 | 3 | // TODO: remove the `Default` similar to the `DefaultEdgePayload` 4 | /// A trait that defines how the payload of a vertex should behave. 5 | pub trait VertexPayload: Clone + PartialEq + std::fmt::Debug { 6 | /// Returns a new default instance without any meaningful data. 7 | fn allocate() -> Self; 8 | } 9 | 10 | /// The default vertex payload can be safely constructed with a default constructor. 11 | /// For vertex payloads this is usually not the case when meaningful positions are required. 12 | pub trait DefaultVertexPayload: VertexPayload + Default {} 13 | 14 | // TODO: use this whenever it is required for the position to be euclidean. 15 | //pub trait IsEuclidean {} 16 | 17 | /// An empty vertex payload if you don't need any vertex information. 18 | /// Notice that your mesh will behave more like a graph without any payload. 19 | // TODO: implement this. Requires the VertexPayload to be weaker and use a separate, stronger trait (e.g., `EuclideanVertexPayload`) for the full payload. 20 | #[derive(Debug, Clone, PartialEq, Default, Copy)] 21 | pub struct EmptyVertexPayload; 22 | -------------------------------------------------------------------------------- /src/mesh/vertex/transform.rs: -------------------------------------------------------------------------------- 1 | use super::Vertex; 2 | use crate::{math::Transformable, mesh::MeshType}; 3 | 4 | impl Transformable for U 5 | where 6 | ::VP: Transformable< 7 | Trans = ::Trans, 8 | Rot = ::Rot, 9 | Vec = ::Vec, 10 | S = ::S, 11 | >, 12 | { 13 | type Rot = ::Rot; 14 | type S = ::S; 15 | type Trans = ::Trans; 16 | type Vec = ::Vec; 17 | 18 | #[inline(always)] 19 | fn transform(&mut self, transform: &Self::Trans) -> &mut Self { 20 | self.payload_mut().transform(transform); 21 | self 22 | } 23 | 24 | #[inline(always)] 25 | fn translate(&mut self, transform: &Self::Vec) -> &mut Self { 26 | self.payload_mut().translate(transform); 27 | self 28 | } 29 | 30 | #[inline(always)] 31 | fn rotate(&mut self, transform: &Self::Rot) -> &mut Self { 32 | self.payload_mut().rotate(transform); 33 | self 34 | } 35 | 36 | #[inline(always)] 37 | fn scale(&mut self, transform: &Self::Vec) -> &mut Self { 38 | self.payload_mut().scale(transform); 39 | self 40 | } 41 | 42 | #[inline(always)] 43 | fn lerp(&mut self, other: &Self, t: Self::S) -> &mut Self { 44 | self.payload_mut().lerp(other.payload(), t); 45 | self 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/operations/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the builder functions for the mesh representation. 2 | 3 | mod extrude; 4 | mod loft; 5 | mod subdivision; 6 | 7 | pub use extrude::*; 8 | pub use loft::*; 9 | pub use subdivision::*; 10 | -------------------------------------------------------------------------------- /src/primitives/misc.rs: -------------------------------------------------------------------------------- 1 | use crate::math::{Scalar, Vector2D}; 2 | use rand::Rng; 3 | 4 | /// Generates a zigzag pattern with `n` vertices, which 5 | /// is the worst case for the sweep line triangulation 6 | pub fn generate_zigzag(n0: usize) -> impl Iterator { 7 | assert!(n0 >= 2 && n0 % 2 == 0); 8 | let n = n0 / 2; 9 | (0..(2 * n)).map(move |i| { 10 | let mut offset = Vec2::S::ZERO; 11 | let mut x = Vec2::S::from_usize(i); 12 | if i > n { 13 | offset = 1.0.into(); 14 | x = Vec2::S::from_usize(2 * n - i); 15 | } 16 | 17 | if i % 2 == 0 { 18 | offset += 2.0.into(); 19 | } 20 | 21 | Vec2::new( 22 | x / Vec2::S::from_usize(n / 2) - 1.0.into(), 23 | offset - 1.0.into(), 24 | ) 25 | }) 26 | } 27 | 28 | /// Generates a star with a random number of vertices between `min_vert` and `max_vert`. 29 | /// The angles are fixed but the radii are random within the given range. 30 | pub fn random_star( 31 | min_vert: usize, 32 | max_vert: usize, 33 | min_r: f32, 34 | max_r: f32, 35 | ) -> impl Iterator { 36 | let mut rng = rand::thread_rng(); 37 | let n = rng.gen_range(min_vert..=max_vert); 38 | 39 | (0..n).into_iter().map(move |i| { 40 | // TODO: which direction should the star be oriented? 41 | let phi = i as f32 / n as f32 * 2.0 * std::f32::consts::PI; 42 | let r = rng.gen_range(min_r..=max_r); 43 | let x = r * phi.cos(); 44 | let y = r * phi.sin(); 45 | Vec2::new(Vec2::S::from(x), Vec2::S::from(y)) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/primitives/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementations of the various primitives that can be used to create a mesh. 2 | 3 | mod misc; 4 | mod plane; 5 | mod polygon; 6 | mod prismatoid; 7 | mod sphere; 8 | 9 | pub use misc::*; 10 | pub use plane::*; 11 | pub use polygon::*; 12 | pub use prismatoid::*; 13 | pub use sphere::*; 14 | -------------------------------------------------------------------------------- /src/primitives/plane.rs: -------------------------------------------------------------------------------- 1 | use crate::mesh::{DefaultEdgePayload, DefaultFacePayload, EuclideanMeshType, MeshTrait}; 2 | 3 | /// A trait for creating plane approximations. 4 | pub trait MakePlane>: 5 | MeshTrait 6 | where 7 | T::EP: DefaultEdgePayload, 8 | T::FP: DefaultFacePayload, 9 | { 10 | // TODO: requires better theory of edges and half edges 11 | /* 12 | /// Generate a subdivided plane made of triangles with given `width` and `height` and 13 | /// `n` and `m` vertices used for the subdivisions, i.e., to subdivide the plane into 14 | /// four columns, use `n = 5`. 15 | fn triangle_plane(width: T::S, height: T::S, n: usize, m: usize) -> Self { 16 | let mut mesh = Self::default(); 17 | let vertical_step = height / T::S::from_usize(m - 1); 18 | let half_horizontal_step = width / T::S::from_usize(n - 1) * T::S::HALF; 19 | let iter = |j: usize| { 20 | (0..n).map(move |i| { 21 | T::VP::from_pos(T::Vec::from_xy( 22 | half_horizontal_step * T::S::from_usize(i * 2 + (j % 2)), 23 | vertical_step * T::S::from_usize(j), 24 | )) 25 | }) 26 | }; 27 | 28 | let (mut first, _) = mesh.insert_path(iter(0)); 29 | for j in 1..m { 30 | let e = mesh.loft_tri_back(first, j % 2 == 0, iter(j)); 31 | first = mesh.edge(e).prev_id(); 32 | } 33 | 34 | mesh 35 | }*/ 36 | 37 | /* 38 | /// Generate a subdivided plane made of quads with given `width` and `height` and 39 | /// `n` and `m` vertices used for the subdivisions, i.e., to subdivide the plane into 40 | /// four columns, use `n = 5`. 41 | fn quad_plane(width: T::S, height: T::S, n: usize, m: usize) -> Self { 42 | let mut mesh = Self::default(); 43 | let vertical_step = height / T::S::from_usize(m - 1); 44 | let horizontal_step = width / T::S::from_usize(n - 1); 45 | let iter = |j: usize| { 46 | (0..n).map(move |i| { 47 | T::VP::from_pos(T::Vec::from_xy( 48 | horizontal_step * T::S::from_usize(i), 49 | vertical_step * T::S::from_usize(j), 50 | )) 51 | }) 52 | }; 53 | 54 | let (mut first, _) = mesh.insert_path(iter(0)); 55 | for j in 1..m { 56 | first = mesh.loft_polygon_back(first, 2, 2, iter(j)); 57 | } 58 | 59 | mesh 60 | } 61 | 62 | /// Generate a subdivided plane made of hexagons with `n` and `m` vertices used for the subdivisions. 63 | /// TODO: Make this more quadratic and less parallelogram. 64 | fn hex_plane(n: usize, m: usize) -> Self { 65 | assert!(n % 2 == 0); 66 | assert!(m >= 2); 67 | let mut mesh = Self::default(); 68 | let row_height = T::S::THREE / T::S::THREE.sqrt(); 69 | let width = T::S::ONE; 70 | let hex_offset = row_height - T::S::TWO / T::S::THREE.sqrt(); 71 | let iter = |offset: usize, j: usize| { 72 | (0..n).map(move |i| { 73 | T::VP::from_pos(T::Vec::from_xy( 74 | width * T::S::from_usize(i + offset), 75 | row_height * T::S::from_usize(j) 76 | + T::S::from_usize((i + j + 1 + offset) % 2) * hex_offset, 77 | )) 78 | }) 79 | }; 80 | 81 | let (mut first, _) = mesh.insert_path(iter(0, 0)); 82 | for j in 1..m { 83 | if j >= 2 { 84 | first = mesh.edge(first).prev_id(); 85 | } 86 | first = mesh.loft_polygon_back(first, 3, 3, iter(j - 1, j)); 87 | } 88 | 89 | mesh 90 | }*/ 91 | } 92 | -------------------------------------------------------------------------------- /src/primitives/polygon.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::{HasPosition, Scalar, Vector}, 3 | mesh::{DefaultEdgePayload, DefaultFacePayload, EuclideanMeshType, MeshTrait, MeshType}, 4 | }; 5 | 6 | /// Calculate the side length of a regular polygon with `n` sides and a given `radius`. 7 | pub fn regular_polygon_sidelength(radius: S, n: usize) -> S { 8 | S::TWO * radius * (S::PI / S::from_usize(n)).sin() 9 | } 10 | 11 | /// Calculate the area of a regular polygon with `n` sides and a given `radius`. 12 | pub fn regular_polygon_area(radius: S, n: usize) -> S { 13 | S::HALF * S::from_usize(n) * radius * radius * (S::TWO * S::PI / S::from_usize(n)).sin() 14 | } 15 | 16 | /// Methods to insert 2D shapes into a mesh. 17 | pub trait Make2dShape>: MeshTrait 18 | where 19 | T::EP: DefaultEdgePayload, 20 | T::FP: DefaultFacePayload, 21 | { 22 | /// Construct a polygon from the given vertices and return the first edge on the outside boundary. 23 | fn insert_polygon(&mut self, vp: impl IntoIterator) -> T::E; 24 | 25 | /// Calls `insert_polygon` on a default mesh. 26 | fn polygon(vp: impl IntoIterator) -> Self { 27 | let mut mesh = Self::default(); 28 | mesh.insert_polygon(vp); 29 | mesh 30 | } 31 | 32 | /// Construct a dihedron (flat degenerate polygon with two faces) from the given vertices. 33 | fn insert_dihedron(&mut self, _vp: impl IntoIterator) -> T::E; 34 | 35 | /// Calls `insert_dihedron` on a default mesh. 36 | fn dihedron(vp: impl IntoIterator) -> Self { 37 | let mut mesh = Self::default(); 38 | mesh.insert_dihedron(vp); 39 | mesh 40 | } 41 | 42 | /// create a regular star, i.e., a regular polygon with two radii 43 | fn insert_regular_star( 44 | &mut self, 45 | inner_radius: T::S, 46 | outer_radius: T::S, 47 | n: usize, 48 | ) -> T::E 49 | where 50 | T: EuclideanMeshType, 51 | { 52 | let pi2n = 2.0 * std::f32::consts::PI / (n as f32); 53 | self.insert_polygon((0..n).into_iter().map(|i| { 54 | let r = if i % 2 == 1 { 55 | outer_radius 56 | } else { 57 | inner_radius 58 | }; 59 | let angle = pi2n * (i as f32); 60 | T::VP::from_pos(T::Vec::from_xy( 61 | r * T::S::from(angle.sin()), 62 | r * T::S::from(angle.cos()), 63 | )) 64 | })) 65 | } 66 | 67 | /// create a regular polygon 68 | fn regular_polygon(radius: T::S, n: usize) -> Self 69 | where 70 | T: EuclideanMeshType, 71 | { 72 | Self::regular_star(radius, radius, n) 73 | } 74 | 75 | /// Calls `insert_regular_star` on a new mesh. 76 | fn regular_star(inner_radius: T::S, outer_radius: T::S, n: usize) -> Self 77 | where 78 | T: EuclideanMeshType, 79 | { 80 | let mut mesh = Self::default(); 81 | mesh.insert_regular_star(inner_radius, outer_radius, n); 82 | mesh 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/tesselate/convex.rs: -------------------------------------------------------------------------------- 1 | use crate::mesh::{Face3d, FaceBasics, MeshType3D, Triangulation, VertexBasics}; 2 | use itertools::Itertools; 3 | 4 | /// Converts the face into a triangle fan. Only works for convex planar faces. 5 | pub fn fan_triangulation( 6 | face: &T::Face, 7 | mesh: &T::Mesh, 8 | indices: &mut Triangulation, 9 | ) { 10 | debug_assert!(face.may_be_curved() || face.is_planar2(mesh)); 11 | debug_assert!(face.is_convex(mesh)); 12 | 13 | let center = face.vertices(mesh).next().unwrap(); 14 | face.vertices(mesh) 15 | .skip(1) 16 | .tuple_windows::<(_, _)>() 17 | .for_each(|(a, b)| indices.insert_triangle(center.id(), a.id(), b.id())); 18 | } 19 | -------------------------------------------------------------------------------- /src/tesselate/min_weight_greedy.rs: -------------------------------------------------------------------------------- 1 | use crate::mesh::{MeshType3D, Triangulation}; 2 | 3 | /// Simple greedy approach to approximate the min-weight triangulation of a polygon by 4 | /// always inserting the shortest non-intersecting chord from a small local neighborhood. 5 | /// Runs in O(n log^2 n) time. 6 | pub fn minweight_greedy( 7 | _face: &T::Face, 8 | _mesh: &T::Mesh, 9 | _indices: &mut Triangulation, 10 | ) { 11 | // TODO: We could also greedily search for the shortest non-intersecting chord and then insert that one. Should give a decent solution. 12 | 13 | // TODO: try to use a segment tree (using axis-parallel rectangles) to speed up the search for intersections (lookup in O(log^2 n) time). 14 | 15 | // TODO: Alternatively (because spatial datastructures might not be nlogn here), 16 | // we could use the monotone partitioning from the sweep line algo and than optimize edge 17 | // lengths in each monotone polygon. After that, we could run a few iterations of local 18 | // search to converge to the next local optimum. 19 | 20 | // TODO: Also try one of the QPTAS algorithms, 21 | // e.g., Remy, Steger: "A quasi-polynomial time approximation scheme for Minimum Weight Triangulation" (2009), https://arxiv.org/pdf/1301.2253 22 | // computes a (1+eps)-approximation in n^O((log^8 n) eps^-5) time. 23 | // even though its probably not suitable for implementation, 24 | // it might give some interesting theorems to improve the greedy algorithm. 25 | 26 | // TODO: Use the fact, that simple polygons with few inner points can be triangulated fast! 27 | // in O(6^k n^5 log n) (Hoffmann and Okamoto 2004) 28 | // in O(n^3 k! k) (Grantson et al 2005) 29 | 30 | // TODO: For a convex polygon, the min-weight triangulation can be found in O(n^3) time using dynamic programming. 31 | // TODO: For a monotone polygon, it is even possible in O(n^2) time. 32 | 33 | todo!("mwt greedy"); 34 | } 35 | -------------------------------------------------------------------------------- /src/tesselate/sweep/chain.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevy-procedural/modelling/72fd86a68d2f11582c214742bd3326743889fdf5/src/tesselate/sweep/chain.rs -------------------------------------------------------------------------------- /src/tesselate/sweep/interval.rs: -------------------------------------------------------------------------------- 1 | use super::monotone::MonotoneTriangulator; 2 | use crate::{ 3 | math::{IndexType, Scalar, Vector, Vector2D}, 4 | mesh::IndexedVertex2D, 5 | }; 6 | 7 | /// This represents a single edge constraining a sweep line interval. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub struct IntervalBoundaryEdge { 10 | pub start: usize, 11 | pub end: usize, 12 | } 13 | 14 | impl IntervalBoundaryEdge { 15 | /// Calculates the x-coordinate of the edge at a given y-coordinate. 16 | /// 17 | /// This method uses linear interpolation to find the x-coordinate on the edge 18 | /// defined by the start and end vertices at the specified y-coordinate. 19 | pub fn x_at_y( 20 | &self, 21 | y: Vec2::S, 22 | vec2s: &Vec>, 23 | ) -> Vec2::S { 24 | let e = vec2s[self.end].vec; 25 | let s = vec2s[self.start].vec; 26 | let dx = e.x() - s.x(); 27 | let dy = e.y() - s.y(); 28 | if dy == Vec2::S::ZERO { 29 | // when parallel to the sweep line, we can just use the x-coordinate of the end vertex 30 | e.x() 31 | } else { 32 | s.x() + dx * (y - s.y()) / dy 33 | } 34 | } 35 | 36 | /// Calculate the parameters of the beam f(y) = a*y + b where y >= c 37 | pub fn beam( 38 | &self, 39 | vec2s: &Vec>, 40 | ) -> Option<(Vec2::S, Vec2::S, Vec2::S)> { 41 | let e = vec2s[self.end].vec; 42 | let s = vec2s[self.start].vec; 43 | let dx = e.x() - s.x(); 44 | let dy = e.y() - s.y(); 45 | if dy == Vec2::S::ZERO { 46 | return None; 47 | } 48 | let a = dx / dy; 49 | let b = s.x() - a * s.y(); 50 | let c = s.y(); 51 | Some((a, b, c)) 52 | } 53 | } 54 | 55 | impl IntervalBoundaryEdge { 56 | pub fn new(start: usize, end: usize) -> Self { 57 | IntervalBoundaryEdge { start, end } 58 | } 59 | } 60 | 61 | /// This represents a single interval of the sweep line. 62 | /// Each interval stores edges that are still work in progress 63 | /// and information in how to connect them to the rest of the mesh. 64 | #[derive(Clone, PartialEq, Eq)] 65 | pub struct SweepLineInterval { 66 | /// The lowest vertex index of the interval. 67 | /// Things can be connected to this vertex when needed. 68 | pub helper: usize, 69 | 70 | /// The edge to the left of the interval 71 | pub left: IntervalBoundaryEdge, 72 | 73 | /// The edge to the right of the interval 74 | pub right: IntervalBoundaryEdge, 75 | 76 | /// There might be a longer chain of edges that connect the left 77 | /// and right edge and are not yet included in generated triangles. 78 | /// Those are stored in the reflex chain and are either leading to the 79 | /// left or to the right edge. 80 | pub chain: MT, 81 | 82 | /// Whether there was a merge that needs to be fixed up. 83 | pub fixup: Option, 84 | } 85 | 86 | impl SweepLineInterval { 87 | /// Check whether the interval contains a position 88 | pub fn contains(&self, pos: &MT::Vec2, vec2s: &Vec>) -> bool { 89 | let p1 = self.left.x_at_y::(pos.y(), vec2s); 90 | // return `false` early to speed things up 91 | if p1 > pos.x() { 92 | return false; 93 | } 94 | let p2 = self.right.x_at_y::(pos.y(), vec2s); 95 | assert!(p1 <= p2); 96 | return pos.x() <= p2; 97 | } 98 | 99 | /// Check if the interval is circular 100 | fn is_circular(&self) -> bool { 101 | (self.left.start == self.right.end && self.right.start == self.left.end) 102 | || (self.left.start == self.right.start && self.left.end == self.right.end) 103 | } 104 | 105 | /// When the intervals are connected, the next vertex must be the end. 106 | pub fn is_end(&self) -> bool { 107 | self.left.end == self.right.end 108 | } 109 | 110 | pub fn sanity_check(&self) -> bool { 111 | assert!(!self.is_circular()); 112 | self.chain 113 | .sanity_check(self.left.start, self.right.start, &self.fixup); 114 | return true; 115 | } 116 | } 117 | 118 | impl std::fmt::Debug for SweepLineInterval { 119 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 120 | write!(f, "lowest: {} ", self.helper)?; 121 | write!(f, "left: {}->{} ", self.left.start, self.left.end)?; 122 | write!(f, "right: {}->{} ", self.right.start, self.right.end)?; 123 | write!(f, "stacks: {:?} ", self.chain)?; 124 | if let Some(fixup) = &self.fixup { 125 | write!(f, "fixup: {:?}", fixup)?; 126 | } 127 | Ok(()) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/tesselate/sweep/mod.rs: -------------------------------------------------------------------------------- 1 | mod chain; 2 | mod interval; 3 | mod monotone; 4 | mod point; 5 | mod status; 6 | mod sweep; 7 | mod vertex_type; 8 | 9 | pub use monotone::*; 10 | pub use sweep::sweep_line_triangulation; 11 | pub use vertex_type::VertexType; 12 | 13 | use super::TesselationMeta; 14 | use crate::{ 15 | math::IndexType, 16 | mesh::{Face3d, FaceBasics, IndexedVertex2D, MeshType3D, Triangulation}, 17 | }; 18 | 19 | /// Meta information for debuggin the sweep algorithm 20 | #[derive(Debug, Clone, PartialEq)] 21 | pub struct SweepMeta { 22 | #[cfg(feature = "sweep_debug")] 23 | /// The type of the vertex in the reflex chain 24 | pub vertex_type: Vec<(V, VertexType)>, 25 | 26 | phantom: std::marker::PhantomData, 27 | } 28 | 29 | impl Default for SweepMeta { 30 | fn default() -> Self { 31 | SweepMeta { 32 | #[cfg(feature = "sweep_debug")] 33 | vertex_type: Vec::new(), 34 | phantom: std::marker::PhantomData, 35 | } 36 | } 37 | } 38 | 39 | impl SweepMeta { 40 | /// Update the type of a vertex 41 | #[cfg(feature = "sweep_debug")] 42 | pub fn update_type(&mut self, i: V, t: VertexType) { 43 | // TODO: Not efficient 44 | for (j, ty) in self.vertex_type.iter_mut() { 45 | if *j == i { 46 | *ty = t; 47 | } 48 | } 49 | } 50 | } 51 | 52 | /// Uses the sweep line triangulation 53 | pub fn sweep_line>( 54 | face: &T::Face, 55 | mesh: &T::Mesh, 56 | indices: &mut Triangulation, 57 | meta: &mut TesselationMeta, 58 | ) { 59 | debug_assert!(face.may_be_curved() || face.is_planar2(mesh)); 60 | 61 | // TODO: Improve performance by directly using the nd-vertices instead of converting to 2d 62 | let vec2s: Vec<_> = face 63 | .vertices_2d(mesh) 64 | .map(|(p, i)| IndexedVertex2D::::new(p, i)) 65 | .collect(); 66 | 67 | sweep_line_triangulation::(indices, &vec2s, &mut meta.sweep); 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | 73 | use crate::{prelude::*, tesselate::sweep::LinearMonoTriangulator}; 74 | 75 | fn verify_triangulation(mesh: &T::Mesh, f: T::F) { 76 | let face = mesh.face(f); 77 | let vec2s = face.vec2s(mesh); 78 | assert!( 79 | T::Poly::from_iter(vec2s.iter().map(|v| v.vec)).is_ccw(), 80 | "Polygon must be counterclockwise" 81 | ); 82 | let mut indices = Vec::new(); 83 | let mut tri = Triangulation::new(&mut indices); 84 | let mut meta = TesselationMeta::default(); 85 | sweep_line::>(face, &mesh, &mut tri, &mut meta); 86 | tri.verify_full::(&vec2s); 87 | } 88 | 89 | /* 90 | #[test] 91 | #[cfg(feature = "nalgebra")] 92 | fn test_font() { 93 | use crate::extensions::nalgebra::*; 94 | 95 | let mut mesh2d = Mesh2d64Curved::new(); 96 | Font::new(include_bytes!("../../../assets/Cochineal-Roman.otf"), 1.0) 97 | .layout_text::<2, MeshType2d64PNUCurved>("F", &mut mesh2d); 98 | self::verify_triangulation::(&mesh2d.to_nd(0.01), 0); 99 | }*/ 100 | } 101 | -------------------------------------------------------------------------------- /src/tesselate/sweep/monotone/mod.rs: -------------------------------------------------------------------------------- 1 | mod delaunay; 2 | mod dynamic; 3 | mod linear; 4 | 5 | pub use delaunay::*; 6 | pub use dynamic::*; 7 | pub use linear::*; 8 | 9 | use crate::{ 10 | math::{IndexType, Vector2D}, 11 | mesh::{IndexedVertex2D, Triangulation}, 12 | }; 13 | 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 15 | enum ChainDirection { 16 | /// The reflex chain is completely on the left 17 | Left, 18 | /// The reflex chain is completely on the right 19 | Right, 20 | /// The reflex chain consists of the first single item having no preference for a side or is empty 21 | None, 22 | } 23 | 24 | /// While a monotone sub-polygon is being processed, the vertices are stored in this data structure. 25 | /// They will come as two chains, one for the left and one for the right side of the polygon. 26 | /// It doesn't have to store all vertices - it's fine to do all the proccessing in 27 | /// the `left` and `right` functions and not doing anything in `finish`. 28 | pub trait MonotoneTriangulator: Sized + std::fmt::Debug + Clone { 29 | /// The index type used in the mesh 30 | type V: IndexType; 31 | 32 | /// The vector type used in the mesh 33 | type Vec2: Vector2D; 34 | 35 | /// Create a new chain with a single value 36 | fn new(v: usize) -> Self; 37 | 38 | /// Get the last inserted element of the opposite chain 39 | /// TODO: Get rid of this. It's confusing to use and only use in the split case. 40 | fn last_opposite(&self) -> usize; 41 | 42 | /// Whether the chain is oriented to the right 43 | fn is_right(&self) -> bool; 44 | 45 | /// Validate the data structure 46 | fn sanity_check(&self, left_start: usize, right_start: usize, fixup: &Option); 47 | 48 | /// Add a new value to the right chain 49 | fn right( 50 | &mut self, 51 | value: usize, 52 | indices: &mut Triangulation, 53 | vec2s: &Vec>, 54 | ); 55 | 56 | /// Add a new value to the left chain 57 | fn left( 58 | &mut self, 59 | value: usize, 60 | indices: &mut Triangulation, 61 | vec2s: &Vec>, 62 | ); 63 | 64 | /// Finish triangulating the monotone polygon 65 | fn finish( 66 | &mut self, 67 | indices: &mut Triangulation, 68 | vec2s: &Vec>, 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /src/tesselate/sweep/point.rs: -------------------------------------------------------------------------------- 1 | use super::VertexType; 2 | use crate::{ 3 | math::{IndexType, Scalar, Vector2D}, 4 | mesh::IndexedVertex2D, 5 | }; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct EventPoint 9 | where 10 | Vec2: Vector2D, 11 | { 12 | /// Current vertex in the face 13 | pub here: usize, 14 | pub prev: usize, 15 | pub next: usize, 16 | 17 | pub vec: Vec2, 18 | 19 | /// Precomputed vertex type 20 | pub vertex_type: VertexType, 21 | } 22 | 23 | impl EventPoint { 24 | pub fn classify(here: usize, vec2s: &Vec>) -> Self { 25 | let prev = (here + vec2s.len() - 1) % vec2s.len(); 26 | let next = (here + 1) % vec2s.len(); 27 | 28 | EventPoint { 29 | here, 30 | vec: vec2s[here].vec, 31 | prev, 32 | next, 33 | vertex_type: VertexType::classify::( 34 | vec2s[prev].vec, 35 | vec2s[here].vec, 36 | vec2s[next].vec, 37 | Vec2::S::EPS * Vec2::S::from(1000.0), 38 | ), 39 | } 40 | } 41 | } 42 | 43 | impl std::cmp::PartialEq for EventPoint { 44 | fn eq(&self, other: &Self) -> bool { 45 | self.vec.y() == other.vec.y() 46 | } 47 | } 48 | 49 | impl std::cmp::Eq for EventPoint {} 50 | 51 | impl std::cmp::PartialOrd for EventPoint { 52 | fn partial_cmp(&self, other: &Self) -> Option { 53 | if let Some(res) = (-self.vec.y()).partial_cmp(&(-other.vec.y())) { 54 | if res == std::cmp::Ordering::Equal { 55 | other.vec.x().partial_cmp(&self.vec.x()) 56 | } else { 57 | Some(res) 58 | } 59 | } else { 60 | None 61 | } 62 | } 63 | } 64 | 65 | impl std::cmp::Ord for EventPoint { 66 | #[inline(always)] 67 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 68 | // TODO: Undefined behavior if float comparison is not defined 69 | if let Some(res) = other.vec.y().partial_cmp(&self.vec.y()) { 70 | if res == std::cmp::Ordering::Equal { 71 | other 72 | .vec 73 | .x() 74 | .partial_cmp(&self.vec.x()) 75 | .unwrap_or(std::cmp::Ordering::Equal) 76 | } else { 77 | res 78 | } 79 | } else { 80 | std::cmp::Ordering::Equal 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/util/deletable.rs: -------------------------------------------------------------------------------- 1 | //! A module for soft-deletable elements. 2 | 3 | use crate::math::IndexType; 4 | 5 | /// A trait for soft-deletable elements. 6 | pub trait Deletable { 7 | /// Returns whether the element is deleted. 8 | fn is_deleted(&self) -> bool; 9 | 10 | /// Marks the element as deleted. 11 | fn delete(&mut self); 12 | 13 | /// Sets the id of the element (un-deletes it). 14 | fn set_id(&mut self, id: I); 15 | 16 | /// Allocates a new, "deleted" instance (it isn't valid) 17 | fn allocate() -> Self; 18 | } 19 | 20 | /// A vector that also keeps track of deleted elements to reallocate them. 21 | #[derive(Debug, Clone)] 22 | pub struct DeletableVector, I: IndexType> { 23 | data: Vec, 24 | deleted: Vec, 25 | } 26 | 27 | impl, I: IndexType> DeletableVector { 28 | /// Creates a new empty vector. 29 | pub fn new() -> Self { 30 | Self { 31 | data: Vec::new(), 32 | deleted: Vec::new(), 33 | } 34 | } 35 | 36 | /// Deletes all elements. 37 | pub fn clear(&mut self) { 38 | self.data.clear(); 39 | self.deleted.clear(); 40 | } 41 | 42 | /// Returns an iterator over the non-deleted elements. 43 | pub fn iter(&self) -> impl Iterator { 44 | self.data.iter().filter(|f| !f.is_deleted()) 45 | } 46 | 47 | /// Returns a mutable iterator over the non-deleted elements. 48 | pub fn iter_mut(&mut self) -> impl Iterator { 49 | self.data.iter_mut().filter(|f| !f.is_deleted()) 50 | } 51 | 52 | /// Returns the requested element. Panics if it doesn't exist or is deleted. 53 | pub fn get(&self, index: I) -> &T { 54 | let v = &self.data[index.index()]; 55 | assert!( 56 | !v.is_deleted(), 57 | "Tried to access deleted element at {}", 58 | index 59 | ); 60 | v 61 | } 62 | 63 | /// Returns whether the element exists and is not deleted. 64 | pub fn has(&self, index: I) -> bool { 65 | let i = index.index(); 66 | i < self.data.len() && !self.data[i].is_deleted() 67 | } 68 | 69 | /// Returns the requested element mutably. Panics if it doesn't exist or is deleted. 70 | pub fn get_mut(&mut self, index: I) -> &mut T { 71 | let v = &mut self.data[index.index()]; 72 | assert!( 73 | !v.is_deleted(), 74 | "Tried to mutably access deleted element at {}", 75 | index 76 | ); 77 | v 78 | } 79 | 80 | /// Returns the number of non-deleted elements. 81 | pub fn len(&self) -> usize { 82 | self.data.len() - self.deleted.len() 83 | } 84 | 85 | /// Returns the maximum index of the non-deleted elements. 86 | pub fn capacity(&self) -> usize { 87 | self.data.len() 88 | } 89 | 90 | /// Allocates a new element, moves the given to that index, sets the new id, and returns the index. 91 | pub fn push(&mut self, mut v: T) -> I { 92 | assert!( 93 | v.is_deleted(), 94 | "Tried to push an element that already has an id" 95 | ); 96 | if let Some(index) = self.deleted.pop() { 97 | v.set_id(index); 98 | self.data[index.index()] = v; 99 | index 100 | } else { 101 | let index = I::new(self.data.len()); 102 | v.set_id(index); 103 | self.data.push(v); 104 | index 105 | } 106 | } 107 | 108 | /// Move the element at the given index. Assumes that the position is allocated and free, i.e., the contents are deleted. 109 | pub fn set(&mut self, index: I, mut v: T) { 110 | assert!( 111 | self.data[index.index()].is_deleted(), 112 | "Tried to overwrite a non-deleted element at {}", 113 | index 114 | ); 115 | assert!( 116 | v.is_deleted(), 117 | "Tried to set an element that already has an id" 118 | ); 119 | v.set_id(index); 120 | self.data[index.index()] = v; 121 | } 122 | 123 | /// Marks the element as deleted and remembers it for reallocation. 124 | pub fn delete_internal(&mut self, f: I) { 125 | self.data[f.index()].delete(); 126 | self.deleted.push(f); 127 | } 128 | 129 | /// Returns the next free index or allocates a new one. 130 | /// The element is not deleted anymore, but it is not valid until it is overwritten. 131 | /// TODO: How can we force the user to overwrite it afterwards? Not writing to it is a memory leak. 132 | pub fn allocate(&mut self) -> I { 133 | if let Some(index) = self.deleted.pop() { 134 | index 135 | } else { 136 | let t = T::allocate(); 137 | debug_assert!(t.is_deleted()); 138 | self.data.push(t); 139 | I::new(self.data.len() - 1) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | //! utility functions 2 | 3 | mod deletable; 4 | 5 | pub use deletable::*; 6 | --------------------------------------------------------------------------------