diff --git a/Cargo.lock b/Cargo.lock index fe4722b..818df42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ "ron", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "thread_local", "tracing", "uuid", @@ -379,7 +379,7 @@ dependencies = [ "ctrlc", "downcast-rs", "log", - "thiserror 2.0.12", + "thiserror 2.0.18", "variadics_please", "wasm-bindgen", "web-sys", @@ -417,7 +417,7 @@ dependencies = [ "ron", "serde", "stackfuture", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "uuid", "wasm-bindgen", @@ -467,7 +467,7 @@ dependencies = [ "derive_more", "encase", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "wgpu-types", ] @@ -497,7 +497,7 @@ dependencies = [ "radsort", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] @@ -554,7 +554,7 @@ dependencies = [ "nonmax", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "variadics_please", ] @@ -593,7 +593,7 @@ dependencies = [ "bevy_time", "bevy_utils", "gilrs", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] @@ -665,7 +665,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] @@ -692,7 +692,7 @@ dependencies = [ "rectangle-pack", "ruzstd", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "wgpu-types", ] @@ -712,7 +712,7 @@ dependencies = [ "derive_more", "log", "smol_str", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -728,7 +728,7 @@ dependencies = [ "bevy_reflect", "bevy_window", "log", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -820,7 +820,7 @@ dependencies = [ "rand_distr", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "variadics_please", ] @@ -844,7 +844,7 @@ dependencies = [ "bytemuck", "hexasphere", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "wgpu-types", ] @@ -897,7 +897,7 @@ dependencies = [ "radsort", "smallvec", "static_assertions", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] @@ -971,7 +971,7 @@ dependencies = [ "serde", "smallvec", "smol_str", - "thiserror 2.0.12", + "thiserror 2.0.18", "uuid", "variadics_please", "wgpu-types", @@ -1034,7 +1034,7 @@ dependencies = [ "send_wrapper", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "variadics_please", "wasm-bindgen", @@ -1071,7 +1071,7 @@ dependencies = [ "bevy_utils", "derive_more", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "uuid", ] @@ -1180,7 +1180,7 @@ dependencies = [ "serde", "smallvec", "sys-locale", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "unicode-bidi", ] @@ -1215,7 +1215,7 @@ dependencies = [ "bevy_utils", "derive_more", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -1249,7 +1249,7 @@ dependencies = [ "nonmax", "smallvec", "taffy", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", ] @@ -1450,6 +1450,8 @@ dependencies = [ "bevy_panorbit_camera", "glam 0.30.9", "rayon", + "serde", + "thiserror 2.0.18", "tobj", ] @@ -2231,6 +2233,9 @@ name = "glam" version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" +dependencies = [ + "serde_core", +] [[package]] name = "glob" @@ -2785,7 +2790,7 @@ dependencies = [ "spirv", "strum", "termcolor", - "thiserror 2.0.12", + "thiserror 2.0.18", "unicode-xid", ] @@ -3991,11 +3996,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -4011,9 +4016,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -4444,7 +4449,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "wgpu-hal", "wgpu-types", ] @@ -4487,7 +4492,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "wasm-bindgen", "web-sys", "wgpu-types", diff --git a/Cargo.toml b/Cargo.toml index 53a3c5c..887c335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,15 @@ rayon = { version = "1.11.0", optional = true } tobj = {version = "4.0.3", optional = true} bevy = { version = "0.16.1", optional = true } bevy_panorbit_camera = { version = "0.26.0", optional = true } +thiserror = "2.0.18" +serde = { version = "1.0.228", features = ["derive"], optional = true } [features] default = [] verbose = [] f32 = [] rayon = ["dep:rayon"] +serde = ["dep:serde", "glam/serde"] bevy = [ "dep:tobj", diff --git a/src/common.rs b/src/common.rs index f9ad083..cb547a0 100644 --- a/src/common.rs +++ b/src/common.rs @@ -23,13 +23,17 @@ mod precision { pub type Vec2u = glam::USizeVec2; pub type Vec3u = glam::USizeVec3; -pub use precision::{Real, Vec2, Vec3, Vec4, Mat3, K_PRECISION}; +pub use precision::{Mat3, Real, Vec2, Vec3, Vec4, K_PRECISION}; pub const K_BEST: Real = Real::MIN; - #[derive(PartialEq)] -pub enum OpType { Add, Subtract, Intersect } +pub enum OpType { + Add, + Subtract, + Intersect, +} +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub struct Half { pub tail: usize, @@ -38,21 +42,62 @@ pub struct Half { } impl Default for Half { - fn default() -> Self { Self { tail: usize::MAX, head: usize::MAX, pair: usize::MAX } } + fn default() -> Self { + Self { + tail: usize::MAX, + head: usize::MAX, + pair: usize::MAX, + } + } } impl Half { - pub fn new(tail: usize, head: usize, pair: usize) -> Self { Self { tail, head, pair } } - pub fn new_without_pair(tail: usize, head: usize) -> Self { Self { tail, head, pair: usize::MAX } } - pub fn is_forward(&self) -> bool { self.tail < self.head } - pub fn tail(&self) -> Option { if self.tail == usize::MAX {None} else {Some(self.tail)} } - pub fn head(&self) -> Option { if self.head == usize::MAX {None} else {Some(self.head)} } - pub fn pair(&self) -> Option { if self.pair == usize::MAX {None} else {Some(self.pair)} } + pub fn new(tail: usize, head: usize, pair: usize) -> Self { + Self { tail, head, pair } + } + pub fn new_without_pair(tail: usize, head: usize) -> Self { + Self { + tail, + head, + pair: usize::MAX, + } + } + pub fn is_forward(&self) -> bool { + self.tail < self.head + } + pub fn tail(&self) -> Option { + if self.tail == usize::MAX { + None + } else { + Some(self.tail) + } + } + pub fn head(&self) -> Option { + if self.head == usize::MAX { + None + } else { + Some(self.head) + } + } + pub fn pair(&self) -> Option { + if self.pair == usize::MAX { + None + } else { + Some(self.pair) + } + } } -pub fn face_of(hid: usize) -> usize { hid / 3 } -pub fn next_of(hid: usize) -> usize { let mut i = hid + 1; if i.is_multiple_of(3) { i -= 3;} i } - +pub fn face_of(hid: usize) -> usize { + hid / 3 +} +pub fn next_of(hid: usize) -> usize { + let mut i = hid + 1; + if i.is_multiple_of(3) { + i -= 3; + } + i +} #[derive(Clone, Debug, Copy)] pub struct Tref { @@ -66,12 +111,14 @@ impl Default for Tref { Self { mid: usize::MAX, fid: usize::MAX, - pid: -1 + pid: -1, } } } -pub fn det2x2(a: &Vec2, b: &Vec2) -> Real { a.x * b.y - a.y * b.x } +pub fn det2x2(a: &Vec2, b: &Vec2) -> Real { + a.x * b.y - a.y * b.x +} pub fn get_aa_proj_matrix(n: &Vec3) -> (Vec3, Vec3) { let a = n.abs(); @@ -79,11 +126,29 @@ pub fn get_aa_proj_matrix(n: &Vec3) -> (Vec3, Vec3) { let r1: Vec3; let r2: Vec3; - if a.z > a.x && a.z > a.y { r1 = Vec3::new(1., 0., 0.); r2 = Vec3::new(0., 1., 0.); m = n.z; } // preserve x, y - else if a.y > a.x { r1 = Vec3::new(0., 0., 1.); r2 = Vec3::new(1., 0., 0.); m = n.y; } // preserve z, x - else { r1 = Vec3::new(0., 1., 0.); r2 = Vec3::new(0., 0., 1.); m = n.x; } // preserve y, z - - if m < 0. { (-r1, r2) } else { (r1, r2) } + if a.z > a.x && a.z > a.y { + r1 = Vec3::new(1., 0., 0.); + r2 = Vec3::new(0., 1., 0.); + m = n.z; + } + // preserve x, y + else if a.y > a.x { + r1 = Vec3::new(0., 0., 1.); + r2 = Vec3::new(1., 0., 0.); + m = n.y; + } + // preserve z, x + else { + r1 = Vec3::new(0., 1., 0.); + r2 = Vec3::new(0., 0., 1.); + m = n.x; + } // preserve y, z + + if m < 0. { + (-r1, r2) + } else { + (r1, r2) + } } pub fn compute_aa_proj(p: &(Vec3, Vec3), v: &Vec3) -> Vec2 { @@ -95,8 +160,14 @@ pub fn is_ccw_2d(p0: &Vec2, p1: &Vec2, p2: &Vec2, t: Real) -> i32 { let v2 = p2 - p0; let area = v1.x * v2.y - v1.y * v2.x; let base = v1.length_squared().max(v2.length_squared()); - if area.powi(2) * 4. <= base * t.powi(2) { return 0; } - if area > 0. { 1 } else { -1 } + if area.powi(2) * 4. <= base * t.powi(2) { + return 0; + } + if area > 0. { + 1 + } else { + -1 + } } pub fn is_ccw_3d(p0: &Vec3, p1: &Vec3, p2: &Vec3, n: &Vec3, t: Real) -> i32 { @@ -105,21 +176,25 @@ pub fn is_ccw_3d(p0: &Vec3, p1: &Vec3, p2: &Vec3, n: &Vec3, t: Real) -> i32 { &compute_aa_proj(&p, p0), &compute_aa_proj(&p, p1), &compute_aa_proj(&p, p2), - t + t, ) } pub fn safe_normalize(v: Vec2) -> Vec2 { let n = v.normalize(); - if n.x.is_finite() && !n.x.is_nan() && - n.y.is_finite() && !n.y.is_nan() { n } - else { Vec2::new(0., 0.) } + if n.x.is_finite() && !n.x.is_nan() && n.y.is_finite() && !n.y.is_nan() { + n + } else { + Vec2::new(0., 0.) + } } pub fn compute_orthogonal(n: Vec3) -> Vec3 { - let b = if n.x.abs() < 0.9 - { Vec3::new(1., 0., 0.) } - else { Vec3::new(0., 1., 0.) }; + let b = if n.x.abs() < 0.9 { + Vec3::new(1., 0., 0.) + } else { + Vec3::new(0., 1., 0.) + }; n.cross(b).normalize() } diff --git a/src/compose/cone.rs b/src/compose/cone.rs index eedddd9..51a3662 100644 --- a/src/compose/cone.rs +++ b/src/compose/cone.rs @@ -1,16 +1,17 @@ //--- Copyright (C) 2025 Saki Komikado , //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. -use std::f64::consts::PI; -use crate::{Manifold, Vec3, Real, compute_orthogonal}; use crate::common::Vec3u; +use crate::manifold::ManifoldError; +use crate::{compute_orthogonal, Manifold, Real, Vec3}; +use std::f64::consts::PI; pub fn generate_cone( apex: Vec3, center: Vec3, radius: Real, divide: usize, -) -> Result { +) -> Result { let d = (PI * 2. / divide as f64) as Real; let n = (center - apex).normalize(); let b1 = compute_orthogonal(n); @@ -30,4 +31,4 @@ pub fn generate_cone( ps.push(apex); ps.push(center); Manifold::new_impl(ps, ts, None, None) -} \ No newline at end of file +} diff --git a/src/compose/cube.rs b/src/compose/cube.rs index 88bee39..fff5e8a 100644 --- a/src/compose/cube.rs +++ b/src/compose/cube.rs @@ -1,27 +1,17 @@ //--- Copyright (C) 2025 Saki Komikado , //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. -use crate::manifold::Manifold; +use crate::manifold::{Manifold, ManifoldError}; -pub fn generate_cube() -> Result { +pub fn generate_cube() -> Result { let ps = [ - -0.5, -0.5, -0.5, - -0.5, -0.5, 0.5, - -0.5, 0.5, -0.5, - -0.5, 0.5, 0.5, - 0.5, -0.5, -0.5, - 0.5, -0.5, 0.5, - 0.5, 0.5, -0.5, - 0.5, 0.5, 0.5 + -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, + -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, ]; let ts = [ - 1, 0, 4, 2, 4, 0, - 1, 3, 0, 3, 1, 5, - 3, 2, 0, 3, 7, 2, - 5, 4, 6, 5, 1, 4, - 6, 4, 2, 7, 6, 2, - 7, 3, 5, 7, 5, 6 + 1, 0, 4, 2, 4, 0, 1, 3, 0, 3, 1, 5, 3, 2, 0, 3, 7, 2, 5, 4, 6, 5, 1, 4, 6, 4, 2, 7, 6, 2, + 7, 3, 5, 7, 5, 6, ]; Manifold::new(&ps, &ts) } diff --git a/src/compose/cylinder.rs b/src/compose/cylinder.rs index 2af6f72..88faaa3 100644 --- a/src/compose/cylinder.rs +++ b/src/compose/cylinder.rs @@ -1,20 +1,24 @@ //--- Copyright (C) 2025 Saki Komikado , //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. +use thiserror::Error; + +use crate::{manifold::ManifoldError, Manifold, Real, Vec3, Vec3u}; use std::f64::consts::PI; -use crate::{Manifold, Vec3, Vec3u, Real}; pub fn generate_cylinder( r: f64, // radius h: f64, // height d0: usize, // sectors d1: usize, // stacks -) -> Result { - if d0 < 3 || d1 < 1 { return Err("sectors must be >= 3 and stacks must be >= 1".into()); } +) -> Result { + if d0 < 3 || d1 < 1 { + return Err(CylinderError::InvalidSectorCount); + } let mut ps = vec![]; let mut ts = vec![]; - ps.push(Vec3::new(0., h as Real * 0.5, 0.)); + ps.push(Vec3::new(0., h as Real * 0.5, 0.)); ps.push(Vec3::new(0., -h as Real * 0.5, 0.)); for i in 0..=d1 { @@ -45,5 +49,16 @@ pub fn generate_cylinder( } } - Manifold::new_impl(ps, ts, None, None) -} \ No newline at end of file + let manifold = Manifold::new_impl(ps, ts, None, None)?; + + Ok(manifold) +} + +#[derive(Debug, Error)] +pub enum CylinderError { + #[error("sectors must be >= 3 and stacks must be >= 1")] + InvalidSectorCount, + + #[error("{0}")] + Manifold(#[from] ManifoldError), +} diff --git a/src/compose/mod.rs b/src/compose/mod.rs index f76049a..4049304 100644 --- a/src/compose/mod.rs +++ b/src/compose/mod.rs @@ -4,34 +4,50 @@ pub mod cone; pub mod cube; +pub mod cylinder; pub mod sphere; pub mod torus; -pub mod cylinder; pub use cone::*; pub use cube::*; +pub use cylinder::*; pub use sphere::*; pub use torus::*; -pub use cylinder::*; -use crate::{Manifold, Vec3, K_PRECISION}; use crate::common::{compute_aa_proj, get_aa_proj_matrix, Vec3u}; +use crate::manifold::ManifoldError; use crate::triangulation::ear_clip::EarClip; use crate::triangulation::Pt; +use crate::{Manifold, Vec3, K_PRECISION}; -pub fn extrude(pts: &[Vec3], offset: Vec3) -> Result { +pub fn extrude(pts: &[Vec3], offset: Vec3) -> Result { let n = Vec3::new(0., 0., 1.); let proj = get_aa_proj_matrix(&n); - let poly = pts.iter().enumerate().map(|(i, p)| Pt {pos: compute_aa_proj(&proj, p), idx: i}).collect::>(); + let poly = pts + .iter() + .enumerate() + .map(|(i, p)| Pt { + pos: compute_aa_proj(&proj, p), + idx: i, + }) + .collect::>(); let idcs = EarClip::new(&[poly], K_PRECISION).triangulate(); let mut oft_ps = vec![]; let mut oft_ts = vec![]; let n = pts.len(); - for p in pts.iter() { oft_ps.push(*p); } - for p in pts.iter() { oft_ps.push(p + offset); } - for i in idcs.iter() { oft_ts.push(Vec3u::new(i.z, i.y, i.x)); } - for i in idcs.iter() { oft_ts.push(Vec3u::new(i.x + n, i.y + n, i.z + n)); } + for p in pts.iter() { + oft_ps.push(*p); + } + for p in pts.iter() { + oft_ps.push(p + offset); + } + for i in idcs.iter() { + oft_ts.push(Vec3u::new(i.z, i.y, i.x)); + } + for i in idcs.iter() { + oft_ts.push(Vec3u::new(i.x + n, i.y + n, i.z + n)); + } for i in 0..n { let j = (i + 1) % n; oft_ts.push(Vec3u::new(i, j, i + n)); @@ -40,12 +56,14 @@ pub fn extrude(pts: &[Vec3], offset: Vec3) -> Result { Manifold::new_impl(oft_ps, oft_ts, None, None) } -pub fn compose(ms: &Vec) -> Result { +pub fn compose(ms: &Vec) -> Result { let mut ps = vec![]; let mut ts = vec![]; let mut offset = 0; for m in ms { - for h in m.hs.iter() { ts.push(h.tail + offset); } + for h in m.hs.iter() { + ts.push(h.tail + offset); + } for p in m.ps.iter() { ps.push(p.x as f64); ps.push(p.y as f64); @@ -57,7 +75,7 @@ pub fn compose(ms: &Vec) -> Result { } pub fn fractal( - hole : &Manifold, + hole: &Manifold, holes: &mut Vec, x: f64, y: f64, @@ -71,17 +89,19 @@ pub fn fractal( m.translate(x, y, 0.); holes.push(m); - if depth == depth_max { return; } + if depth == depth_max { + return; + } for xy in [ (x - w, y - w), - (x - w, y ), + (x - w, y), (x - w, y + w), - (x , y + w), + (x, y + w), (x + w, y + w), - (x + w, y ), + (x + w, y), (x + w, y - w), - (x , y - w) + (x, y - w), ] { fractal(hole, holes, xy.0, xy.1, w, depth + 1, depth_max); } diff --git a/src/compose/sphere.rs b/src/compose/sphere.rs index e8c1919..cfb7073 100644 --- a/src/compose/sphere.rs +++ b/src/compose/sphere.rs @@ -1,15 +1,19 @@ //--- Copyright (C) 2025 Saki Komikado , //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. +use thiserror::Error; + +use crate::{manifold::ManifoldError, Manifold, Real, Vec3, Vec3u}; use std::collections::HashMap; use std::f64::consts::PI; -use crate::{Manifold, Vec3, Vec3u, Real}; pub fn generate_uv_sphere( d0: usize, // sectors d1: usize, // stacks -) -> Result { - if d0 < 3 || d1 < 2 { return Err("sectors must be >= 3 and stacks must be >= 2".into()); } +) -> Result { + if d0 < 3 || d1 < 2 { + return Err(UVSphereError::InvalidSectorCount); + } let mut ps = vec![]; let mut ts = vec![]; @@ -42,25 +46,27 @@ pub fn generate_uv_sphere( } } - Manifold::new_impl(ps, ts, None, None) + let manifold = Manifold::new_impl(ps, ts, None, None)?; + + Ok(manifold) } -pub fn generate_icosphere(subdivisions: u32) -> Result { +pub fn generate_icosphere(subdivisions: u32) -> Result { let phi = ((1. + 5.0f32.sqrt()) / 2.) as Real; let mut ps = vec![ - Vec3::new(-1.0, phi, 0.0).normalize(), - Vec3::new( 1.0, phi, 0.0).normalize(), - Vec3::new(-1.0, -phi, 0.0).normalize(), - Vec3::new( 1.0, -phi, 0.0).normalize(), - Vec3::new( 0.0, -1.0, phi).normalize(), - Vec3::new( 0.0, 1.0, phi).normalize(), - Vec3::new( 0.0, -1.0, -phi).normalize(), - Vec3::new( 0.0, 1.0, -phi).normalize(), - Vec3::new( phi, 0.0, -1.0).normalize(), - Vec3::new( phi, 0.0, 1.0).normalize(), - Vec3::new(-phi, 0.0, -1.0).normalize(), - Vec3::new(-phi, 0.0, 1.0).normalize(), + Vec3::new(-1.0, phi, 0.0).normalize(), + Vec3::new(1.0, phi, 0.0).normalize(), + Vec3::new(-1.0, -phi, 0.0).normalize(), + Vec3::new(1.0, -phi, 0.0).normalize(), + Vec3::new(0.0, -1.0, phi).normalize(), + Vec3::new(0.0, 1.0, phi).normalize(), + Vec3::new(0.0, -1.0, -phi).normalize(), + Vec3::new(0.0, 1.0, -phi).normalize(), + Vec3::new(phi, 0.0, -1.0).normalize(), + Vec3::new(phi, 0.0, 1.0).normalize(), + Vec3::new(-phi, 0.0, -1.0).normalize(), + Vec3::new(-phi, 0.0, 1.0).normalize(), ]; let mut ts = vec![ @@ -88,14 +94,18 @@ pub fn generate_icosphere(subdivisions: u32) -> Result { let mut cache = HashMap::new(); - let get_midpoint = | - vid1: usize, - vid2: usize, - verts: &mut Vec, - cache: &mut HashMap<(usize, usize), usize> - | { - let e = if vid1 < vid2 { (vid1, vid2) } else { (vid2, vid1) }; - if let Some(&i) = cache.get(&e) { return i; } + let get_midpoint = |vid1: usize, + vid2: usize, + verts: &mut Vec, + cache: &mut HashMap<(usize, usize), usize>| { + let e = if vid1 < vid2 { + (vid1, vid2) + } else { + (vid2, vid1) + }; + if let Some(&i) = cache.get(&e) { + return i; + } let v1 = verts[vid1]; let v2 = verts[vid2]; @@ -121,4 +131,13 @@ pub fn generate_icosphere(subdivisions: u32) -> Result { } Manifold::new_impl(ps, ts, None, None) -} \ No newline at end of file +} + +#[derive(Debug, Error)] +pub enum UVSphereError { + #[error("sectors must be >= 3 and stacks must be >= 2")] + InvalidSectorCount, + + #[error("{0}")] + Manifold(#[from] ManifoldError), +} diff --git a/src/compose/torus.rs b/src/compose/torus.rs index 3c9df5d..99c0044 100644 --- a/src/compose/torus.rs +++ b/src/compose/torus.rs @@ -1,16 +1,15 @@ //--- Copyright (C) 2025 Saki Komikado , //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. +use crate::{manifold::ManifoldError, Manifold, Real, Vec3, Vec3u}; use std::f64::consts::PI; -use crate::{Manifold, Vec3, Vec3u, Real}; pub fn generate_torus( r0: f64, // major radius r1: f64, // minor radius d0: usize, // rings d1: usize, // sectors -) -> Result { - +) -> Result { let mut ps = Vec::with_capacity(d0 * d1); let mut ts = Vec::with_capacity(d0 * d1 * 6); @@ -32,9 +31,9 @@ pub fn generate_torus( let ni = (i + 1) % d0; for j in 0..d1 { let nj = (j + 1) % d1; - let v0 = i * d1 + j; - let v1 = i * d1 + nj; - let v2 = ni * d1 + j; + let v0 = i * d1 + j; + let v1 = i * d1 + nj; + let v2 = ni * d1 + j; let v3 = ni * d1 + nj; ts.push(Vec3u::new(v0, v1, v2)); ts.push(Vec3u::new(v1, v3, v2)); @@ -42,4 +41,4 @@ pub fn generate_torus( } Manifold::new_impl(ps, ts, None, None) -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 81af9c8..b1a291a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,50 +5,42 @@ #![allow(clippy::cast_abs_to_unsigned)] #![allow(unused_braces)] -mod manifold; -mod triangulation; -mod simplification; -mod common; mod boolean03; mod boolean45; +mod common; mod compose; +mod manifold; +mod simplification; mod tests; +mod triangulation; + +use thiserror::Error; use crate::boolean03::boolean03; use crate::boolean45::boolean45; -use crate::simplification::simplify_topology; -use crate::triangulation::triangulate; use crate::common::*; use crate::manifold::*; +use crate::simplification::simplify_topology; +use crate::triangulation::triangulate; +use crate::triangulation::TriangulationError; -pub use crate::common::{Real, Vec2, Vec3, Vec4, Mat3, K_PRECISION}; +pub use crate::common::{Mat3, Real, Vec2, Vec3, Vec4, K_PRECISION}; pub mod prelude { pub use crate::common::OpType; - pub use crate::manifold::Manifold; - pub use crate::compute_boolean; pub use crate::compose::{ - compose, - fractal, - extrude, - generate_cone, - generate_cube, - generate_torus, - generate_cylinder, - generate_uv_sphere, - generate_icosphere, + compose, extrude, fractal, generate_cone, generate_cube, generate_cylinder, + generate_icosphere, generate_torus, generate_uv_sphere, }; + pub use crate::compute_boolean; + pub use crate::manifold::Manifold; } -pub fn compute_boolean( - mp: &Manifold, - mq: &Manifold, - op: OpType, -) -> Result { +pub fn compute_boolean(mp: &Manifold, mq: &Manifold, op: OpType) -> Result { let eps = mp.eps.max(mq.eps); let tol = mp.tol.max(mq.tol); - let b03 = boolean03(mp, mq, &op); + let b03 = boolean03(mp, mq, &op); let mut b45 = boolean45(mp, mq, &b03, &op); let mut trg = triangulate(mp, mq, &b45, eps)?; @@ -59,20 +51,31 @@ pub fn compute_boolean( &mut trg.rs, b45.nv_from_p, b45.nv_from_q, - eps + eps, ); - cleanup_unused_verts( - &mut b45.ps, - &mut trg.hs - ); + cleanup_unused_verts(&mut b45.ps, &mut trg.hs); - Manifold::new_impl( + let manifold = Manifold::new_impl( b45.ps, - trg.hs.chunks(3).map(|hs| Vec3u::new(hs[0].tail, hs[1].tail, hs[2].tail)).collect(), + trg.hs + .chunks(3) + .map(|hs| Vec3u::new(hs[0].tail, hs[1].tail, hs[2].tail)) + .collect(), Some(eps), - Some(tol) - ) + Some(tol), + )?; + + Ok(manifold) +} + +#[derive(Debug, Error)] +pub enum BooleanError { + #[error("{0}")] + Trangulate(#[from] TriangulationError), + + #[error("{0}")] + Manifold(#[from] ManifoldError), } //pub fn compute_boolean_from_raw_data( @@ -92,8 +95,3 @@ pub fn compute_boolean( // }; // compute_boolean(&mp, &mq, op) //} - - - - - diff --git a/src/manifold/bounds.rs b/src/manifold/bounds.rs index 4d77dd7..7dbf64f 100644 --- a/src/manifold/bounds.rs +++ b/src/manifold/bounds.rs @@ -4,8 +4,12 @@ use crate::{Real, Vec2, Vec3}; #[derive(Clone, Debug)] -pub enum Query { Bb(BBox), Pt(BPos) } +pub enum Query { + Bb(BBox), + Pt(BPos), +} +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub struct BBox { pub id: Option, @@ -21,16 +25,28 @@ pub struct BPos { impl BBox { pub fn default() -> Self { - BBox { id: None, min: Vec3::MAX, max: Vec3::MIN } + BBox { + id: None, + min: Vec3::MAX, + max: Vec3::MIN, + } } - + pub fn new(id: Option, pts: &[Vec3]) -> Self { - let mut b = BBox { id, min: Vec3::MAX, max: Vec3::MIN }; - for pt in pts { b.union(pt); } + let mut b = BBox { + id, + min: Vec3::MAX, + max: Vec3::MIN, + }; + for pt in pts { + b.union(pt); + } b } - pub fn size(&self) -> Vec3 { self.max - self.min } + pub fn size(&self) -> Vec3 { + self.max - self.min + } pub fn scale(&self) -> Real { let s = self.size(); @@ -40,33 +56,38 @@ impl BBox { pub fn overlaps(&self, q: &Query) -> bool { match q { Query::Bb(b) => self.min.cmple(b.max).all() && self.max.cmpge(b.min).all(), - Query::Pt(p) => { // only evaluates xy axis - self.min.x <= p.pos.x && self.min.y <= p.pos.y && - self.max.x >= p.pos.x && self.max.y >= p.pos.y + Query::Pt(p) => { + // only evaluates xy axis + self.min.x <= p.pos.x + && self.min.y <= p.pos.y + && self.max.x >= p.pos.x + && self.max.y >= p.pos.y } } } pub fn union(&mut self, p: &Vec3) { - if p.x.is_nan() { return; } + if p.x.is_nan() { + return; + } self.min = self.min.min(*p); self.max = self.max.max(*p); } pub fn longest_dim(&self) -> usize { let s = self.size(); - if s.x > s.y && s.x > s.z { 0 } - else if s.y > s.z { 1 } - else { 2 } + if s.x > s.y && s.x > s.z { + 0 + } else if s.y > s.z { + 1 + } else { + 2 + } } } - pub fn union_bbs(b0: &BBox, b1: &BBox) -> BBox { let min = b0.min.min(b1.min); let max = b0.max.max(b1.max); BBox { id: None, min, max } } - - - diff --git a/src/manifold/collider.rs b/src/manifold/collider.rs index c5813f6..eb4ffc6 100644 --- a/src/manifold/collider.rs +++ b/src/manifold/collider.rs @@ -125,6 +125,7 @@ fn build_internal_boxes( } } +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub struct MortonCollider { pub node_bb: Vec, diff --git a/src/manifold/hmesh.rs b/src/manifold/hmesh.rs index ce0e6b0..649620a 100644 --- a/src/manifold/hmesh.rs +++ b/src/manifold/hmesh.rs @@ -2,9 +2,11 @@ //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. #![allow(clippy::needless_range_loop)] +use crate::{Real, Vec2u, Vec3, Vec3u}; +#[cfg(feature = "rayon")] +use rayon::prelude::*; use std::f64::consts::PI; -use crate::{Vec3, Vec2u, Vec3u, Real}; -#[cfg(feature = "rayon")] use rayon::prelude::*; +use thiserror::Error; /// Hmesh preserves the order of pos and idx in any cases. /// Edges are ordered so as the edge is forward (tail idx < head idx) @@ -27,24 +29,33 @@ fn edge_topology( e2v: &mut Vec, e2f: &mut Vec, f2e: &mut Vec, -) -> Result<(), String> { - if pos.is_empty() { return Err("empty pos matrix".into()); } - if idx.is_empty() { return Err("empty idx matrix".into()); } +) -> Result<(), HmeshError> { + if pos.is_empty() { + return Err(HmeshError::EmptyPositionMatrix); + } + if idx.is_empty() { + return Err(HmeshError::EmptyIndexMatrix); + } let mut ett: Vec<[usize; 4]> = vec![]; for (i, idx_) in idx.iter().enumerate() { - for j in 0..3 { - let mut v1 = idx_[j]; - let mut v2 = idx_[(j + 1) % 3]; - if v1 > v2 { std::mem::swap(&mut v1, &mut v2); } - ett.push([v1, v2, i, j]); - }} + for j in 0..3 { + let mut v1 = idx_[j]; + let mut v2 = idx_[(j + 1) % 3]; + if v1 > v2 { + std::mem::swap(&mut v1, &mut v2); + } + ett.push([v1, v2, i, j]); + } + } ett.sort(); let mut ne = 1; for i in 0..ett.len() - 1 { - if !(ett[i][0] == ett[i + 1][0] && ett[i][1] == ett[i + 1][1]) { ne += 1; } + if !(ett[i][0] == ett[i + 1][0] && ett[i][1] == ett[i + 1][1]) { + ne += 1; + } } e2v.resize(ne, Vec2u::MAX); @@ -54,13 +65,13 @@ fn edge_topology( let mut i = 0; while i < ett.len() { - if i == ett.len() - 1 || !((ett[i][0] == ett[i+1][0]) && (ett[i][1] == ett[i + 1][1])) { + if i == ett.len() - 1 || !((ett[i][0] == ett[i + 1][0]) && (ett[i][1] == ett[i + 1][1])) { // Border edge let [v1, v2, i, j] = ett[i]; e2v[ne][0] = v1; e2v[ne][1] = v2; e2f[ne][0] = i; - f2e[i][j] = ne; + f2e[i][j] = ne; } else { let r1 = ett[i]; let r2 = ett[i + 1]; @@ -95,10 +106,7 @@ fn edge_topology( } impl Hmesh { - pub fn new( - pos: &[Vec3], - idx: &[Vec3u], - ) -> Result { + pub fn new(pos: &[Vec3], idx: &[Vec3u]) -> Result { let mut e2v = Default::default(); let mut e2f = Default::default(); let mut f2e = Default::default(); @@ -109,9 +117,9 @@ impl Hmesh { let ne = e2v.len(); let nh = e2v.len() * 2; let np = 3; - let mut v2h = vec![usize::MAX; nv]; - let mut e2h = vec![usize::MAX; ne]; - let mut f2h = vec![usize::MAX; nf]; + let mut v2h = vec![usize::MAX; nv]; + let mut e2h = vec![usize::MAX; ne]; + let mut f2h = vec![usize::MAX; nf]; let mut next = vec![usize::MAX; nh]; let mut prev = vec![usize::MAX; nh]; let mut twin = vec![usize::MAX; nh]; @@ -121,45 +129,53 @@ impl Hmesh { let mut face = vec![usize::MAX; nh]; for it in 0..nf { - for ip in 0..np { - let ih_bgn = it * np; - let iv = idx[it][ip]; - let ie = f2e[it][ip]; - let ih = ih_bgn + ip; - next[ih] = ih_bgn + (ip + 1) % np; - prev[ih] = ih_bgn + (ip + np - 1) % np; - head[ih] = idx[it][(ip + 1) % np]; - tail[ih] = iv; - edge[ih] = ie; - face[ih] = it; - if f2h[it] == usize::MAX { f2h[it] = ih; } - if v2h[iv] == usize::MAX { v2h[iv] = ih; } - if e2h[ie] == usize::MAX { e2h[ie] = ih; } - else { - twin[ih] = e2h[ie]; - twin[e2h[ie]] = ih; + for ip in 0..np { + let ih_bgn = it * np; + let iv = idx[it][ip]; + let ie = f2e[it][ip]; + let ih = ih_bgn + ip; + next[ih] = ih_bgn + (ip + 1) % np; + prev[ih] = ih_bgn + (ip + np - 1) % np; + head[ih] = idx[it][(ip + 1) % np]; + tail[ih] = iv; + edge[ih] = ie; + face[ih] = it; + if f2h[it] == usize::MAX { + f2h[it] = ih; + } + if v2h[iv] == usize::MAX { + v2h[iv] = ih; + } + if e2h[ie] == usize::MAX { + e2h[ie] = ih; + } else { + twin[ih] = e2h[ie]; + twin[e2h[ie]] = ih; + } } - }} + } if twin.iter().any(|v| v == &usize::MAX) { - return Err("Input mesh must not contain boundary edges.".into()); + return Err(HmeshError::ContainsBoundaryEdges); } let mut half = vec![]; - for i in 0..nh { half.push(i); } + for i in 0..nh { + half.push(i); + } let mut vns = vec![Vec3::ZERO; nv]; let mut fns = vec![Vec3::ZERO; nf]; #[cfg(feature = "rayon")] fns.par_iter_mut().enumerate().for_each(|(i, n)| { - let ih = f2h[i]; - let p2 = pos[head[ih]]; - let p1 = pos[tail[ih]]; - let p0 = pos[tail[prev[ih]]]; - let x = p2 - p1; - let t = (p1 - p0) * -1.; - *n = x.cross(t).normalize(); - }); + let ih = f2h[i]; + let p2 = pos[head[ih]]; + let p1 = pos[tail[ih]]; + let p0 = pos[tail[prev[ih]]]; + let x = p2 - p1; + let t = (p1 - p0) * -1.; + *n = x.cross(t).normalize(); + }); #[cfg(not(feature = "rayon"))] for i in 0..nf { @@ -173,28 +189,58 @@ impl Hmesh { } for i in 0..nf { - for j in 0..3 { - let i_curr = idx[i][j]; - let v_prev = pos[idx[i][(j + 2) % 3]]; - let v_curr = pos[i_curr]; - let v_next = pos[idx[i][(j + 1) % 3]]; - let e_curr = (v_next - v_curr).normalize(); - let e_prev = (v_curr - v_prev).normalize(); - if e_curr.is_nan() || e_prev.is_nan() { continue; } - let dot = -e_prev.dot(e_curr); - let phi = if dot >= 1. { 0. } - else if dot <= -1. { PI as Real } - else { dot.acos() }; - vns[i_curr] += fns[i] * phi; - }} - + for j in 0..3 { + let i_curr = idx[i][j]; + let v_prev = pos[idx[i][(j + 2) % 3]]; + let v_curr = pos[i_curr]; + let v_next = pos[idx[i][(j + 1) % 3]]; + let e_curr = (v_next - v_curr).normalize(); + let e_prev = (v_curr - v_prev).normalize(); + if e_curr.is_nan() || e_prev.is_nan() { + continue; + } + let dot = -e_prev.dot(e_curr); + let phi = if dot >= 1. { + 0. + } else if dot <= -1. { + PI as Real + } else { + dot.acos() + }; + vns[i_curr] += fns[i] * phi; + } + } #[cfg(feature = "rayon")] vns.par_iter_mut().for_each(|n| *n = n.normalize_or_zero()); #[cfg(not(feature = "rayon"))] - for n in &mut vns { *n = n.normalize_or_zero(); } + for n in &mut vns { + *n = n.normalize_or_zero(); + } - Ok(Hmesh{ nv, nf, nh, twin, head, tail, half, vns, fns }) + Ok(Hmesh { + nv, + nf, + nh, + twin, + head, + tail, + half, + vns, + fns, + }) } } + +#[derive(Debug, Error)] +pub enum HmeshError { + #[error("empty pos matrix")] + EmptyPositionMatrix, + + #[error("empty idx matrix")] + EmptyIndexMatrix, + + #[error("Input mesh must not contain boundary edges.")] + ContainsBoundaryEdges, +} diff --git a/src/manifold/mod.rs b/src/manifold/mod.rs index 9dc5c8e..1b80473 100644 --- a/src/manifold/mod.rs +++ b/src/manifold/mod.rs @@ -1,18 +1,22 @@ //--- Copyright (C) 2025 Saki Komikado , //--- This Source Code Form is subject to the terms of the Mozilla Public License v.2.0. -pub mod hmesh; pub mod bounds; pub mod collider; +pub mod hmesh; +use super::hmesh::Hmesh; +use crate::collider::{morton_code, MortonCollider, K_NO_CODE}; +use crate::manifold::hmesh::HmeshError; +use crate::{next_of, Half, Mat3, Real, Vec3, Vec3u, K_PRECISION}; +use bounds::BBox; +#[cfg(feature = "rayon")] +use rayon::prelude::*; use std::cmp::Ordering; use std::collections::HashMap; -use bounds::BBox; -use crate::collider::{morton_code, MortonCollider, K_NO_CODE}; -use crate::{Real, Half, Vec3, Vec3u, K_PRECISION, next_of, Mat3}; -use super::hmesh::Hmesh; -#[cfg(feature = "rayon")] use rayon::prelude::*; +use thiserror::Error; +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub struct Manifold { pub ps: Vec, // positions @@ -31,21 +35,25 @@ pub struct Manifold { } impl Manifold { - pub fn new(pos: &[f64], idx: &[usize]) -> Result { - - if pos.len() % 3 != 0 { return Err("pos must be a multiple of 3".into()); } - if idx.len() % 3 != 0 { return Err("idx must be a multiple of 3".into()); } + pub fn new(pos: &[f64], idx: &[usize]) -> Result { + if pos.len() % 3 != 0 { + return Err(ManifoldError::PositionArrayNotMultipleOf3); + } + if idx.len() % 3 != 0 { + return Err(ManifoldError::IndexArrayNotMultipleOf3); + } // dedup vertices - let mut hash = HashMap::with_capacity(pos.len() / 3); - let mut weld = Vec::with_capacity(pos.len() / 3); + let mut hash = HashMap::with_capacity(pos.len() / 3); + let mut weld = Vec::with_capacity(pos.len() / 3); let mut rmap = vec![0; pos.len()]; for (i, p) in pos.chunks(3).enumerate() { let v = Vec3::new(p[0] as Real, p[1] as Real, p[2] as Real); let k = (v.x.to_bits(), v.y.to_bits(), v.z.to_bits()); - if let Some(&w) = hash.get(&k) { rmap[i] = w; } - else { + if let Some(&w) = hash.get(&k) { + rmap[i] = w; + } else { let n = weld.len(); weld.push(v); hash.insert(k, n); @@ -64,15 +72,19 @@ impl Manifold { } pub fn new_impl( - ps : Vec, + ps: Vec, idx: Vec, eps: Option, tol: Option, - ) -> Result { + ) -> Result { let bb = BBox::new(None, &ps); let (mut f_bb, mut f_mt) = compute_face_morton(&ps, &idx, &bb); let hm = sort_faces(&ps, &idx, &mut f_bb, &mut f_mt)?; - let hs = hm.half.iter().map(|&i| Half::new(hm.tail[i], hm.head[i], hm.twin[i])).collect::>(); + let hs = hm + .half + .iter() + .map(|&i| Half::new(hm.tail[i], hm.head[i], hm.twin[i])) + .collect::>(); let mut e = K_PRECISION * bb.scale(); e = if e.is_finite() { e } else { -1. }; @@ -97,28 +109,39 @@ impl Manifold { coplanar, }; - if !mfd.is_manifold() { return Err("The input mesh is not manifold".into()); } + if !mfd.is_manifold() { + return Err(ManifoldError::InputNotManifold); + } Ok(mfd) } pub fn get_indices(&self) -> Vec { - self.hs.chunks(3).map(|cs| Vec3u::new(cs[0].tail, cs[1].tail, cs[2].tail)).collect() + self.hs + .chunks(3) + .map(|cs| Vec3u::new(cs[0].tail, cs[1].tail, cs[2].tail)) + .collect() } pub fn set_epsilon(&mut self, min_epsilon: Real, use_single: bool) { let scl = self.bounding_box.scale(); let mut e = min_epsilon.max(K_PRECISION * scl); e = if e.is_finite() { e } else { -1. }; - let t = if use_single { e.max(Real::EPSILON * scl) } else { e }; + let t = if use_single { + e.max(Real::EPSILON * scl) + } else { + e + }; self.eps = e; self.tol = self.tol.max(t); } pub fn is_manifold(&self) -> bool { self.hs.iter().enumerate().all(|(i, h)| { - if h.tail().is_none() || h.head().is_none() { return true; } + if h.tail().is_none() || h.head().is_none() { + return true; + } match h.pair() { - None => { false }, + None => false, Some(pair) => { let mut good = true; good &= self.hs[pair].pair() == Some(i); @@ -144,21 +167,37 @@ impl Manifold { } pub fn scale(&mut self, x: f64, y: f64, z: f64) { - let p = self.ps.iter().map(|p| Vec3::new(p.x * x as Real, p.y * y as Real, p.z * z as Real)).collect(); + let p = self + .ps + .iter() + .map(|p| Vec3::new(p.x * x as Real, p.y * y as Real, p.z * z as Real)) + .collect(); *self = Manifold::new_impl(p, self.get_indices(), None, None).unwrap(); } } -fn compute_face_morton( - pos: &[Vec3], - idx: &[Vec3u], - bb: &BBox -) -> (Vec, Vec) { +#[derive(Debug, Error)] +pub enum ManifoldError { + #[error("The input mesh is not manifold")] + InputNotManifold, + + #[error("pos must be a multiple of 3")] + PositionArrayNotMultipleOf3, + + #[error("idx must be a multiple of 3")] + IndexArrayNotMultipleOf3, + + #[error("Failed to construct Hmesh: {0:?}")] + Hmesh(#[from] HmeshError), +} + +fn compute_face_morton(pos: &[Vec3], idx: &[Vec3u], bb: &BBox) -> (Vec, Vec) { let n = idx.len(); let mut bbs = vec![BBox::default(); n]; let mut mts = vec![0; n]; - #[cfg(feature = "rayon")] { + #[cfg(feature = "rayon")] + { bbs.par_iter_mut() .zip(mts.par_iter_mut()) .zip(idx.par_iter()) @@ -173,7 +212,8 @@ fn compute_face_morton( }); } - #[cfg(not(feature = "rayon"))] { + #[cfg(not(feature = "rayon"))] + { for (i, f) in idx.iter().enumerate() { let p0 = pos[f.x]; let p1 = pos[f.y]; @@ -185,7 +225,6 @@ fn compute_face_morton( } } - (bbs, mts) } @@ -193,29 +232,30 @@ fn sort_faces( pos: &[Vec3], idx: &[Vec3u], face_bboxes: &mut Vec, - face_morton: &mut Vec -) -> Result { + face_morton: &mut Vec, +) -> Result { let mut map = (0..face_morton.len()).collect::>(); map.sort_by_key(|&i| face_morton[i]); - *face_bboxes = map.iter().map(|&i| face_bboxes[i].clone()).collect::>(); + *face_bboxes = map + .iter() + .map(|&i| face_bboxes[i].clone()) + .collect::>(); *face_morton = map.iter().map(|&i| face_morton[i]).collect::>(); - Hmesh::new(pos, &map.iter().map(|&i| idx[i]).collect::>()) + let hmesh = Hmesh::new(pos, &map.iter().map(|&i| idx[i]).collect::>())?; + Ok(hmesh) } -fn compute_coplanar_idx( - ps: &[Vec3], - ns: &[Vec3], - hs: &[Half], - tol: Real -) -> Vec { +fn compute_coplanar_idx(ps: &[Vec3], ns: &[Vec3], hs: &[Half], tol: Real) -> Vec { let nt = hs.len() / 3; let mut priority = vec![]; let mut res = vec![-1; nt]; for t in 0..nt { let i = t * 3; - let area = if hs[i].tail().is_none() { 0.} else { + let area = if hs[i].tail().is_none() { + 0. + } else { let p0 = ps[hs[i].tail]; let p1 = ps[hs[i].head]; let p2 = ps[hs[i + 1].head]; @@ -228,7 +268,9 @@ fn compute_coplanar_idx( let mut interior = vec![]; for (_, t) in priority.iter() { - if res[*t] != -1 { continue; } + if res[*t] != -1 { + continue; + } res[*t] = *t as i32; let i = t * 3; @@ -242,12 +284,17 @@ fn compute_coplanar_idx( let h1 = next_of(hs[hi].pair); let t1 = h1 / 3; - if res[t1] != -1 { continue; } + if res[t1] != -1 { + continue; + } if (ps[hs[h1].head] - p).dot(n).abs() < tol { res[t1] = *t as i32; - if interior.last().copied() == Some(hs[h1].pair) { interior.pop(); } - else { interior.push(h1); } + if interior.last().copied() == Some(hs[h1].pair) { + interior.pop(); + } else { + interior.push(h1); + } interior.push(next_of(h1)); } } @@ -255,21 +302,22 @@ fn compute_coplanar_idx( res } -pub fn cleanup_unused_verts( - ps: &mut Vec, - hs: &mut Vec -) { +pub fn cleanup_unused_verts(ps: &mut Vec, hs: &mut Vec) { let bb = BBox::new(None, ps); let mt = ps.iter().map(|p| morton_code(p, &bb)).collect::>(); let mut new2old = (0..ps.len()).collect::>(); let mut old2new = vec![0; ps.len()]; new2old.sort_by_key(|&i| mt[i]); - for (new, &old) in new2old.iter().enumerate() { old2new[old] = new; } + for (new, &old) in new2old.iter().enumerate() { + old2new[old] = new; + } // reindex verts for h in hs.iter_mut() { - if h.pair().is_none() { continue; } + if h.pair().is_none() { + continue; + } h.tail = old2new[h.tail]; h.head = old2new[h.head]; } @@ -285,4 +333,3 @@ pub fn cleanup_unused_verts( *ps = new2old.iter().map(|&i| ps[i]).collect(); *hs = hs.iter().filter(|h| h.pair().is_some()).cloned().collect(); } - diff --git a/src/triangulation/mod.rs b/src/triangulation/mod.rs index 2455098..f23fd4d 100644 --- a/src/triangulation/mod.rs +++ b/src/triangulation/mod.rs @@ -5,13 +5,18 @@ pub mod ear_clip; pub mod flat_tree; pub mod tri_halfs; -use std::collections::{BTreeMap, VecDeque}; use crate::boolean45::Boolean45; -use crate::{Manifold, Vec2, Vec3, Vec3u, Half, Tref, get_aa_proj_matrix, compute_aa_proj, is_ccw_3d, Real}; use crate::triangulation::ear_clip::EarClip; +#[cfg(feature = "rayon")] +use crate::triangulation::tri_halfs::tri_halfs_multi; use crate::triangulation::tri_halfs::tri_halfs_single; -#[cfg(feature = "rayon")] use rayon::prelude::*; -#[cfg(feature = "rayon")] use crate::triangulation::tri_halfs::tri_halfs_multi; +use crate::{ + compute_aa_proj, get_aa_proj_matrix, is_ccw_3d, Half, Manifold, Real, Tref, Vec2, Vec3, Vec3u, +}; +#[cfg(feature = "rayon")] +use rayon::prelude::*; +use std::collections::{BTreeMap, VecDeque}; +use thiserror::Error; pub struct Triangulation { pub hs: Vec, @@ -24,9 +29,9 @@ pub fn triangulate( mq: &Manifold, b45: &Boolean45, eps: Real, -) -> Result { - - #[cfg(feature = "rayon")] { +) -> Result { + #[cfg(feature = "rayon")] + { let (mut ts, mut rs, ns) = (0..b45.hid_per_f.len() - 1) .into_par_iter() .map(|fid| { @@ -46,10 +51,15 @@ pub fn triangulate( }, ); update_reference(mp, mq, &mut rs); - Ok(Triangulation { hs: tri_halfs_multi(&mut ts), ns, rs }) + Ok(Triangulation { + hs: tri_halfs_multi(&mut ts), + ns, + rs, + }) } - #[cfg(not(feature = "rayon"))] { + #[cfg(not(feature = "rayon"))] + { let mut ts = vec![]; let mut ns = vec![]; let mut rs = vec![]; @@ -64,21 +74,20 @@ pub fn triangulate( ts.extend(t); } update_reference(mp, mq, &mut rs); - Ok(Triangulation { hs: tri_halfs_single(&ts), ns, rs }) + Ok(Triangulation { + hs: tri_halfs_single(&ts), + ns, + rs, + }) } - } -fn process_face( - b45: &Boolean45, - fid: usize, - eps: Real -) -> Vec { +fn process_face(b45: &Boolean45, fid: usize, eps: Real) -> Vec { let e0 = b45.hid_per_f[fid] as usize; let e1 = b45.hid_per_f[fid + 1] as usize; match e1 - e0 { - 3 => single_triangulate(b45, e0), - 4 => square_triangulate(b45, fid, eps), + 3 => single_triangulate(b45, e0), + 4 => square_triangulate(b45, fid, eps), _ => general_triangulate(b45, fid, eps), } } @@ -99,7 +108,9 @@ fn assemble_halfs(hs: &[Half], hid_f: &[i32], fid: usize) -> Vec> { let mut hid1 = 0; loop { if hid1 == hid0 { - if v2h.is_empty() { break; } + if v2h.is_empty() { + break; + } hid0 = v2h.first_entry().unwrap().get().back().copied().unwrap(); hid1 = hid0; loops.push(Vec::new()); @@ -111,10 +122,7 @@ fn assemble_halfs(hs: &[Half], hid_f: &[i32], fid: usize) -> Vec> { loops } -fn single_triangulate( - b45: &Boolean45, - hid: usize -) -> Vec { +fn single_triangulate(b45: &Boolean45, hid: usize) -> Vec { let mut idcs = [hid, hid + 1, hid + 2]; let mut tails = vec![]; let mut heads = vec![]; @@ -122,7 +130,9 @@ fn single_triangulate( tails.push(b45.hs[*id].tail); heads.push(b45.hs[*id].head); } - if heads[0] == tails[2] { idcs.swap(1, 2); } + if heads[0] == tails[2] { + idcs.swap(1, 2); + } vec![Vec3u::new( b45.hs[idcs[0]].tail, @@ -131,18 +141,14 @@ fn single_triangulate( )] } -fn square_triangulate( - b45: &Boolean45, - fid: usize, - eps: Real -) -> Vec { +fn square_triangulate(b45: &Boolean45, fid: usize, eps: Real) -> Vec { let ccw = |tri: Vec3u| { is_ccw_3d( &b45.ps[b45.hs[tri[0]].tail], &b45.ps[b45.hs[tri[1]].tail], &b45.ps[b45.hs[tri[2]].tail], &b45.ns[fid], - eps + eps, ) >= 0 }; @@ -158,57 +164,57 @@ fn square_triangulate( } else if ccw(tris[1][0]) && ccw(tris[1][1]) { let diag0 = b45.ps[b45.hs[q[0]].tail] - b45.ps[b45.hs[q[2]].tail]; let diag1 = b45.ps[b45.hs[q[1]].tail] - b45.ps[b45.hs[q[3]].tail]; - if diag0.length() > diag1.length() { choice = 1; } + if diag0.length() > diag1.length() { + choice = 1; + } } - tris[choice].iter().map(|t| Vec3u::new( - b45.hs[t.x].tail, - b45.hs[t.y].tail, - b45.hs[t.z].tail - )).collect() + tris[choice] + .iter() + .map(|t| Vec3u::new(b45.hs[t.x].tail, b45.hs[t.y].tail, b45.hs[t.z].tail)) + .collect() } -fn general_triangulate( - b45: &Boolean45, - fid: usize, - eps: Real -) -> Vec { - let proj = get_aa_proj_matrix(&b45.ns[fid]); +fn general_triangulate(b45: &Boolean45, fid: usize, eps: Real) -> Vec { + let proj = get_aa_proj_matrix(&b45.ns[fid]); let loops = assemble_halfs(&b45.hs, &b45.hid_per_f, fid); - let polys = loops.iter().map(|poly| - poly.iter().map(|&e| { - let i = b45.hs[e].tail; - let p = compute_aa_proj(&proj, &b45.ps[i]); - Pt { pos: p, idx: e } - }).collect() - ).collect::>>(); - - EarClip::new(&polys, eps).triangulate().iter().map(|t| Vec3u::new( - b45.hs[t.x].tail, - b45.hs[t.y].tail, - b45.hs[t.z].tail - )).collect() + let polys = loops + .iter() + .map(|poly| { + poly.iter() + .map(|&e| { + let i = b45.hs[e].tail; + let p = compute_aa_proj(&proj, &b45.ps[i]); + Pt { pos: p, idx: e } + }) + .collect() + }) + .collect::>>(); + + EarClip::new(&polys, eps) + .triangulate() + .iter() + .map(|t| Vec3u::new(b45.hs[t.x].tail, b45.hs[t.y].tail, b45.hs[t.z].tail)) + .collect() } - #[derive(Debug, Clone)] pub struct Pt { pub pos: Vec2, - pub idx: usize + pub idx: usize, } -fn update_reference( - mp: &Manifold, - mq: &Manifold, - rs: &mut[Tref], -) { +fn update_reference(mp: &Manifold, mq: &Manifold, rs: &mut [Tref]) { for r in rs.iter_mut() { let fid = r.fid; - let pq = r.mid == 0; - r.pid = if pq { mp.coplanar[fid] } else { mq.coplanar[fid] }; + let pq = r.mid == 0; + r.pid = if pq { + mp.coplanar[fid] + } else { + mq.coplanar[fid] + }; } } - - - +#[derive(Debug, Error)] +pub enum TriangulationError {}