From 9dfd7ec9776bf1b33939597579bc25097966758c Mon Sep 17 00:00:00 2001 From: Nick Ufer Date: Sat, 23 Nov 2024 15:48:40 +0100 Subject: [PATCH] refactor: splits project up into multiple crates feat: adds manifold boolean operation and transformation functions --- Cargo.toml | 23 +- build.rs | 10 + crates/macros/Cargo.toml | 17 + crates/macros/LICENSE | 1 + crates/macros/src/lib.rs | 66 ++++ crates/tests/Cargo.toml | 12 + crates/tests/LICENSE | 1 + crates/tests/src/manifold.rs | 1 + crates/tests/src/manifold3d/manifold.rs | 96 ++++++ crates/tests/src/manifold3d/mod.rs | 1 + crates/types/Cargo.toml | 18 + crates/types/LICENSE | 1 + crates/types/src/lib.rs | 2 + crates/types/src/manifold/mod.rs | 1 + crates/types/src/manifold/vertex.rs | 19 ++ crates/types/src/math/matrix4x3.rs | 42 +++ crates/types/src/math/mod.rs | 17 + crates/types/src/math/non_negative_num.rs | 77 +++++ crates/types/src/math/normalized_angle.rs | 75 +++++ crates/types/src/math/point2.rs | 74 +++++ crates/types/src/math/point3.rs | 95 ++++++ crates/types/src/math/positive_num.rs | 107 ++++++ crates/types/src/math/vec2.rs | 75 +++++ crates/types/src/math/vec3.rs | 78 +++++ src/bounding_box.rs | 41 ++- src/error.rs | 64 ++-- src/lib.rs | 5 +- src/manifold.rs | 382 +++++++++++++++++++++- src/polygons.rs | 2 +- src/quality.rs | 26 +- src/simple_polygon.rs | 10 +- src/types.rs | 282 ---------------- 32 files changed, 1352 insertions(+), 369 deletions(-) create mode 100644 build.rs create mode 100644 crates/macros/Cargo.toml create mode 120000 crates/macros/LICENSE create mode 100644 crates/macros/src/lib.rs create mode 100644 crates/tests/Cargo.toml create mode 120000 crates/tests/LICENSE create mode 100644 crates/tests/src/manifold.rs create mode 100644 crates/tests/src/manifold3d/manifold.rs create mode 100644 crates/tests/src/manifold3d/mod.rs create mode 100644 crates/types/Cargo.toml create mode 120000 crates/types/LICENSE create mode 100644 crates/types/src/lib.rs create mode 100644 crates/types/src/manifold/mod.rs create mode 100644 crates/types/src/manifold/vertex.rs create mode 100644 crates/types/src/math/matrix4x3.rs create mode 100644 crates/types/src/math/mod.rs create mode 100644 crates/types/src/math/non_negative_num.rs create mode 100644 crates/types/src/math/normalized_angle.rs create mode 100644 crates/types/src/math/point2.rs create mode 100644 crates/types/src/math/point3.rs create mode 100644 crates/types/src/math/positive_num.rs create mode 100644 crates/types/src/math/vec2.rs create mode 100644 crates/types/src/math/vec3.rs delete mode 100644 src/types.rs diff --git a/Cargo.toml b/Cargo.toml index 58b9179..802ef8e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,16 +3,27 @@ name = "manifold3d" description = "Bindings for Manifold - a Geometry library for topological robustness" homepage = "https://github.com/NickUfer/manifold3d-rs" license = "Apache-2.0" -version = "0.0.2" +version = "0.0.3" edition = "2021" +[lib] +test = false +crate-type = ["cdylib", "rlib", "staticlib"] + [dependencies] -thiserror = "2.0" -num-traits = "0.2.19" +manifold3d-types = { path = "crates/types", version = "0.0.1" } +manifold3d-macros = { path = "crates/macros", version = "0.0.1" } manifold3d-sys = { version = "0.0.3" } -nalgebra = { version = "0.33.0", optional = true } +thiserror = "2.0.3" [features] -nalgebra_interop = ["nalgebra"] +nalgebra_interop = ["manifold3d-types/nalgebra_interop"] export = ["manifold3d-sys/export"] -parallel = ["manifold3d-sys/parallel"] \ No newline at end of file +parallel = ["manifold3d-sys/parallel"] + +[workspace] +members = [ + "crates/macros", + "crates/tests", + "crates/types" +] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..323b3d5 --- /dev/null +++ b/build.rs @@ -0,0 +1,10 @@ +use std::env; + +fn main() { + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + + if target_arch == "wasm32" && target_os == "emscripten" { + println!("cargo:rustc-link-arg=--no-entry"); + } +} diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml new file mode 100644 index 0000000..366dc6c --- /dev/null +++ b/crates/macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "manifold3d-macros" +description = "Macros for the manifold3d crate. An internal dependency" +homepage = "https://github.com/NickUfer/manifold3d-rs" +repository = "https://github.com/NickUfer/manifold3d-rs/tree/master/crates/macros" +license = "Apache-2.0" +version = "0.0.1" +edition = "2021" +include = ["/src", "/LICENSE"] + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0.89" +quote = "1.0.37" +proc-macro2 = "1.0.92" \ No newline at end of file diff --git a/crates/macros/LICENSE b/crates/macros/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/crates/macros/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs new file mode 100644 index 0000000..e425926 --- /dev/null +++ b/crates/macros/src/lib.rs @@ -0,0 +1,66 @@ +use proc_macro::TokenStream; +use quote::quote; +use std::sync::atomic::AtomicU64; +use std::sync::Mutex; +use syn::ItemStruct; + +static UNIQUE_COUNTER: Mutex = Mutex::new(AtomicU64::new(0)); + +#[proc_macro_attribute] +pub fn manifold_warp(_attr: TokenStream, input: TokenStream) -> TokenStream { + let structt = syn::parse_macro_input!(input as ItemStruct); + let struct_ident = structt.ident.clone(); + let struct_name = struct_ident.to_string(); + + let unique_id = match UNIQUE_COUNTER.lock() { + Ok(guard) => guard.fetch_add(1, std::sync::atomic::Ordering::SeqCst), + Err(e) => panic!("Could not lock unique counter: {}", e), + }; + + let extern_c_fn_ident = proc_macro2::Ident::new( + format!( + "manifold3d_manifold_extern_c_warp_fn_{}_{}", + struct_name.to_ascii_lowercase(), + unique_id + ) + .as_str(), + proc_macro2::Span::call_site(), + ); + + let output = quote!( + #structt + + const _: () = { + use manifold3d::types::manifold::vertex::WarpImpl; + + #[no_mangle] + #[doc(hidden)] + pub unsafe extern "C" fn #extern_c_fn_ident( + x: f64, + y: f64, + z: f64, + ctx: *mut ::std::os::raw::c_void + ) -> manifold3d::sys::ManifoldVec3 { + let warp = &*(ctx as *mut #struct_ident); + let result = warp.warp_vertex(manifold3d::types::math::Point3::new(x, y, z)); + result.into() + } + + #[automatically_derived] + impl manifold3d::types::manifold::vertex::ExternCWarpFn for #struct_ident { + fn extern_c_warp_fn(&self) -> unsafe extern "C" fn( + f64, + f64, + f64, + *mut std::os::raw::c_void + ) -> manifold3d::sys::ManifoldVec3 { + #extern_c_fn_ident + } + } + }; + + #[automatically_derived] + impl manifold3d::types::manifold::vertex::Warp for #struct_ident {} + ); + output.into() +} diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml new file mode 100644 index 0000000..3124704 --- /dev/null +++ b/crates/tests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "manifold3d-tests" +description = "Tests for the manifold3d crate. An internal dependency" +homepage = "https://github.com/NickUfer/manifold3d-rs" +repository = "https://github.com/NickUfer/manifold3d-rs/tree/master/crates/tests" +license = "Apache-2.0" +version = "0.0.1" +edition = "2021" +include = ["/src", "/LICENSE"] + +[dependencies] +manifold3d = { path = "../..", version = "0.0.3" } \ No newline at end of file diff --git a/crates/tests/LICENSE b/crates/tests/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/crates/tests/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/crates/tests/src/manifold.rs b/crates/tests/src/manifold.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/tests/src/manifold.rs @@ -0,0 +1 @@ + diff --git a/crates/tests/src/manifold3d/manifold.rs b/crates/tests/src/manifold3d/manifold.rs new file mode 100644 index 0000000..f907b22 --- /dev/null +++ b/crates/tests/src/manifold3d/manifold.rs @@ -0,0 +1,96 @@ +use manifold3d::macros::manifold_warp; +use manifold3d::types::manifold::vertex; +use manifold3d::{types, BooleanOperation, Manifold}; +use std::pin::Pin; + +#[test] +fn test_translation() { + let original = Manifold::new_cuboid(1u8, 1u8, 1u8, true); + let translated = original.translate(types::math::Vec3::new(1.0, -1.0, 3.0)); + + assert_eq!( + translated.bounding_box().min_point(), + types::math::Point3::new(0.5, -1.5, 2.5) + ); + assert_eq!( + translated.bounding_box().max_point(), + types::math::Point3::new(1.5, -0.5, 3.5) + ); +} + +#[test] +fn test_boolean_subtraction() { + let manifold = Manifold::new_cuboid(1u8, 1u8, 1u8, true); + let expected_bounding_box = manifold.bounding_box(); + + let other = + Manifold::new_cuboid(1u8, 1u8, 1u8, true).translate(types::math::Vec3::new(0.0, 0.5, 0.0)); + let result = manifold.boolean(&other, BooleanOperation::Subtract); + let result_bounding_box = result.bounding_box(); + + assert_eq!( + result_bounding_box.min_point(), + expected_bounding_box.min_point() + ); + assert_eq!(result_bounding_box.max_point().x, 0.5); + assert_eq!(result_bounding_box.max_point().y, 0.0); + assert_eq!(result_bounding_box.max_point().z, 0.5); +} + +#[test] +fn test_batch_boolean_subtraction() { + let manifold = Manifold::new_cuboid(1u8, 1u8, 1u8, true); + + // Removes all edges to form a cross + let others = vec![ + Manifold::new_cuboid(1u8, 1u8, 1u8, true) + .translate(types::math::Vec3::new(0.75, 0.75, 0.0)), + Manifold::new_cuboid(1u8, 1u8, 1u8, true) + .translate(types::math::Vec3::new(-0.75, -0.75, 0.0)), + Manifold::new_cuboid(1u8, 1u8, 1u8, true) + .translate(types::math::Vec3::new(0.75, -0.75, 0.0)), + Manifold::new_cuboid(1u8, 1u8, 1u8, true) + .translate(types::math::Vec3::new(-0.75, 0.75, 0.0)), + ]; + let result = manifold.batch_boolean(&others, BooleanOperation::Subtract); + + assert_eq!(result.vertex_count(), 24); +} + +#[test] +fn test_linear_warping() { + #[manifold_warp] + pub struct TranslationWarp { + translation: types::math::Vec3, + } + + impl vertex::WarpImpl for TranslationWarp { + fn warp_vertex(&self, vertex: types::math::Point3) -> types::math::Point3 { + let result = types::math::Point3::new( + vertex.x + self.translation.x, + vertex.y + self.translation.y, + vertex.z + self.translation.z, + ); + result + } + } + + let manifold = Manifold::new_cuboid(1u8, 1u8, 1u8, true); + let expected_bounding_box = manifold.bounding_box(); + + let translation_warp = TranslationWarp { + translation: types::math::Vec3::new(1.0, 1.0, 1.0), + }; + + let translation_warp = Pin::new(&translation_warp); + let result = manifold.warp(translation_warp); + let result_bounding_box = result.bounding_box(); + assert_eq!( + result_bounding_box.min_point(), + expected_bounding_box.min_point() + 1.0 + ); + assert_eq!( + result_bounding_box.max_point(), + expected_bounding_box.max_point() + 1.0 + ); +} diff --git a/crates/tests/src/manifold3d/mod.rs b/crates/tests/src/manifold3d/mod.rs new file mode 100644 index 0000000..8c425dc --- /dev/null +++ b/crates/tests/src/manifold3d/mod.rs @@ -0,0 +1 @@ +mod manifold; diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml new file mode 100644 index 0000000..329e5d5 --- /dev/null +++ b/crates/types/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "manifold3d-types" +description = "Types for the manifold3d crate. An internal dependency" +homepage = "https://github.com/NickUfer/manifold3d-rs" +repository = "https://github.com/NickUfer/manifold3d-rs/tree/master/crates/types" +license = "Apache-2.0" +version = "0.0.1" +edition = "2021" +include = ["/src", "/LICENSE"] + +[dependencies] +thiserror = "2.0.3" +num-traits = "0.2.19" +nalgebra = { version = "0.33.0", optional = true } +manifold3d-sys = "0.0.3" + +[features] +nalgebra_interop = ["nalgebra"] \ No newline at end of file diff --git a/crates/types/LICENSE b/crates/types/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/crates/types/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs new file mode 100644 index 0000000..c63360f --- /dev/null +++ b/crates/types/src/lib.rs @@ -0,0 +1,2 @@ +pub mod manifold; +pub mod math; diff --git a/crates/types/src/manifold/mod.rs b/crates/types/src/manifold/mod.rs new file mode 100644 index 0000000..f838c36 --- /dev/null +++ b/crates/types/src/manifold/mod.rs @@ -0,0 +1 @@ +pub mod vertex; diff --git a/crates/types/src/manifold/vertex.rs b/crates/types/src/manifold/vertex.rs new file mode 100644 index 0000000..8802484 --- /dev/null +++ b/crates/types/src/manifold/vertex.rs @@ -0,0 +1,19 @@ +use crate::math; +use manifold3d_sys::ManifoldVec3; + +pub trait Warp: WarpImpl + ExternCWarpFn {} + +pub trait WarpImpl { + fn warp_vertex(&self, vertex: math::Point3) -> math::Point3; +} + +pub trait ExternCWarpFn { + fn extern_c_warp_fn( + &self, + ) -> unsafe extern "C" fn( + arg1: f64, + arg2: f64, + arg3: f64, + arg4: *mut ::std::os::raw::c_void, + ) -> ManifoldVec3; +} diff --git a/crates/types/src/math/matrix4x3.rs b/crates/types/src/math/matrix4x3.rs new file mode 100644 index 0000000..fcf6d45 --- /dev/null +++ b/crates/types/src/math/matrix4x3.rs @@ -0,0 +1,42 @@ +use crate::math; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Matrix4x3 { + pub rows: [math::Vec3; 4], +} + +impl Matrix4x3 { + pub fn new(rows: [math::Vec3; 4]) -> Self { + Self { rows } + } +} + +#[cfg(feature = "nalgebra_interop")] +impl From> for Matrix4x3 { + fn from(matrix: nalgebra::Matrix4x3) -> Self { + Matrix4x3 { + rows: [ + math::Vec3 { + x: matrix.m11, + y: matrix.m12, + z: matrix.m13, + }, + math::Vec3 { + x: matrix.m21, + y: matrix.m22, + z: matrix.m23, + }, + math::Vec3 { + x: matrix.m31, + y: matrix.m32, + z: matrix.m33, + }, + math::Vec3 { + x: matrix.m41, + y: matrix.m42, + z: matrix.m43, + }, + ], + } + } +} diff --git a/crates/types/src/math/mod.rs b/crates/types/src/math/mod.rs new file mode 100644 index 0000000..81f0296 --- /dev/null +++ b/crates/types/src/math/mod.rs @@ -0,0 +1,17 @@ +mod matrix4x3; +mod non_negative_num; +mod normalized_angle; +mod point2; +mod point3; +mod positive_num; +mod vec2; +mod vec3; + +pub use matrix4x3::*; +pub use non_negative_num::*; +pub use normalized_angle::*; +pub use point2::*; +pub use point3::*; +pub use positive_num::*; +pub use vec2::*; +pub use vec3::*; diff --git a/crates/types/src/math/non_negative_num.rs b/crates/types/src/math/non_negative_num.rs new file mode 100644 index 0000000..8cc8e6a --- /dev/null +++ b/crates/types/src/math/non_negative_num.rs @@ -0,0 +1,77 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NonNegativeNumError { + #[error("the value is not positive")] + NonPositiveValue, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct NonNegativeNum(T); + +impl NonNegativeNum { + pub fn new(value: T) -> Result { + if !Self::is_valid(value) { + return Err(NonNegativeNumError::NonPositiveValue); + } + Ok(NonNegativeNum(value)) + } + + #[inline(always)] + fn is_valid(value: T) -> bool { + value >= T::zero() + } +} + +impl NonNegativeNum { + pub fn get(&self) -> T { + self.0 + } +} + +macro_rules! impl_positive_num_from { + ($ttype:ident, $underlying_primitive:ident, ($($from_type:ty),+)) => { + $( + impl From<$from_type> for $ttype { + fn from(value: $from_type) -> Self { + NonNegativeNum($underlying_primitive::from(value)) + } + } + )+ + }; +} + +macro_rules! impl_positive_num_try_from { + ($ttype:ident, $underlying_primitive:ident, ($($from_type:ty),+)) => { + $( + impl TryFrom<$from_type> for $ttype { + type Error = NonNegativeNumError; + + fn try_from(value: $from_type) -> Result { + $ttype::new($underlying_primitive::from(value)) + } + } + )+ + }; +} + +macro_rules! impl_into_primitive { + ($ttype:ident, $underlying_primitive:ident) => { + #[allow(clippy::from_over_into)] + impl Into<$underlying_primitive> for $ttype { + fn into(self) -> $underlying_primitive { + self.get() + } + } + }; +} + +pub type NonNegativeI32 = NonNegativeNum; +impl_into_primitive!(NonNegativeI32, i32); +impl_positive_num_from!(NonNegativeI32, i32, (u8, u16)); +impl_positive_num_try_from!(NonNegativeI32, i32, (i8, i16, i32)); + +pub type NonNegativeF64 = NonNegativeNum; +impl_into_primitive!(NonNegativeF64, f64); +impl_positive_num_from!(NonNegativeF64, f64, (u8, u16, u32)); +impl_positive_num_try_from!(NonNegativeF64, f64, (i8, i16, i32, f32, f64)); diff --git a/crates/types/src/math/normalized_angle.rs b/crates/types/src/math/normalized_angle.rs new file mode 100644 index 0000000..a1dc536 --- /dev/null +++ b/crates/types/src/math/normalized_angle.rs @@ -0,0 +1,75 @@ +use crate::math; +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +/// Represents an angle, measured in degrees, constrained to the range [-360.0, 360.0]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct NormalizedAngle(f64); + +impl NormalizedAngle { + pub fn from_degrees(value: T) -> Self + where + T: Into, + { + NormalizedAngle(Self::normalize(value)) + } + + pub fn as_degrees(&self) -> f64 { + self.0 + } + + fn normalize(value: impl Into) -> f64 { + let mut value = value.into() % 360.0; + if value < 0.0 { + value = 360.0 - value; + } + value + } + + pub fn get(&self) -> f64 { + self.0 + } +} + +impl From> for NormalizedAngle +where + T: num_traits::Num + PartialOrd + Copy, + f64: From, +{ + fn from(value: math::PositiveNum) -> Self { + NormalizedAngle(f64::from(value.get()) % 360.0) + } +} + +impl Add for NormalizedAngle { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::from_degrees(self.0 + rhs.0) + } +} + +impl AddAssign for NormalizedAngle { + fn add_assign(&mut self, rhs: Self) { + self.0 = Self::normalize(self.0 + rhs.0); + } +} + +impl Sub for NormalizedAngle { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self::from_degrees(self.0 - rhs.0) + } +} + +impl SubAssign for NormalizedAngle { + fn sub_assign(&mut self, rhs: Self) { + self.0 = Self::normalize(self.0 - rhs.0); + } +} + +impl Into for NormalizedAngle { + fn into(self) -> f64 { + self.get() + } +} diff --git a/crates/types/src/math/point2.rs b/crates/types/src/math/point2.rs new file mode 100644 index 0000000..e9f9985 --- /dev/null +++ b/crates/types/src/math/point2.rs @@ -0,0 +1,74 @@ +use manifold3d_sys::ManifoldVec2; +use std::ops::{Add, Sub}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Point2 { + pub x: f64, + pub y: f64, +} + +impl Point2 { + pub fn new(x: f64, y: f64) -> Self { + Self { x, y } + } +} + +impl From for Point2 { + fn from(value: ManifoldVec2) -> Self { + Point2 { + x: value.x, + y: value.y, + } + } +} + +impl From for ManifoldVec2 { + fn from(value: Point2) -> Self { + ManifoldVec2 { + x: value.x, + y: value.y, + } + } +} + +impl Add for Point2 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Point2::new(self.x + rhs.x, self.y + rhs.y) + } +} + +impl Add for Point2 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn add(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Point2::new(self.x + value, self.y + value) + } +} + +impl Sub for Point2 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Point2::new(self.x - rhs.x, self.y - rhs.y) + } +} + +impl Sub for Point2 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Point2::new(self.x - value, self.y - value) + } +} diff --git a/crates/types/src/math/point3.rs b/crates/types/src/math/point3.rs new file mode 100644 index 0000000..1a9f476 --- /dev/null +++ b/crates/types/src/math/point3.rs @@ -0,0 +1,95 @@ +use manifold3d_sys::ManifoldVec3; +use std::ops::{Add, Sub}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Point3 { + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl Point3 { + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self { x, y, z } + } +} + +impl From for Point3 { + fn from(value: ManifoldVec3) -> Self { + Point3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} + +impl From for ManifoldVec3 { + fn from(value: Point3) -> Self { + ManifoldVec3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} + +impl Add for Point3 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Point3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + } +} + +impl Add for Point3 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn add(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Point3::new(self.x + value, self.y + value, self.z + value) + } +} + +impl Sub for Point3 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Point3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} + +impl Sub for Point3 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Point3::new(self.x - value, self.y - value, self.z - value) + } +} + +#[cfg(feature = "nalgebra_interop")] +impl From> for Point3 { + fn from(value: nalgebra::Point3) -> Self { + Point3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} + +#[cfg(feature = "nalgebra_interop")] +impl From for nalgebra::Point3 { + fn from(value: Point3) -> Self { + nalgebra::Point3::new(value.x, value.y, value.z) + } +} diff --git a/crates/types/src/math/positive_num.rs b/crates/types/src/math/positive_num.rs new file mode 100644 index 0000000..41308cc --- /dev/null +++ b/crates/types/src/math/positive_num.rs @@ -0,0 +1,107 @@ +use crate::math; +use std::cmp::Ordering; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum PositiveNumError { + #[error("the value is not positive")] + NonPositiveValue, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PositiveNum(T); + +impl PositiveNum { + pub fn new(value: T) -> Result { + if !Self::is_valid(value) { + return Err(math::PositiveNumError::NonPositiveValue); + } + Ok(PositiveNum(value)) + } + + #[inline(always)] + fn is_valid(value: T) -> bool { + value > T::zero() + } +} + +impl PositiveNum { + pub fn get(&self) -> T { + self.0 + } +} + +macro_rules! impl_positive_num_from { + ($ttype:ident, $underlying_primitive:ident, ($($from_type:ty),+)) => { + $( + impl From<$from_type> for $ttype { + fn from(value: $from_type) -> Self { + PositiveNum($underlying_primitive::from(value)) + } + } + )+ + }; +} + +macro_rules! impl_positive_num_try_from { + ($ttype:ident, $underlying_primitive:ident, ($($from_type:ty),+)) => { + $( + impl TryFrom<$from_type> for $ttype { + type Error = PositiveNumError; + + fn try_from(value: $from_type) -> Result { + $ttype::new($underlying_primitive::from(value)) + } + } + )+ + }; +} + +macro_rules! impl_into_primitive { + ($ttype:ident, $underlying_primitive:ident) => { + #[allow(clippy::from_over_into)] + impl Into<$underlying_primitive> for $ttype { + fn into(self) -> $underlying_primitive { + self.get() + } + } + }; +} + +macro_rules! impl_partial_eq { + ($ttype:ident, $underlying_primitive:ident, ($($eq_type:ty),+)) => { + $( + impl PartialEq<$eq_type> for $ttype { + fn eq(&self, other: &$eq_type) -> bool { + self.get().eq(&(*other as $underlying_primitive)) + } + } + )+ + }; +} + +macro_rules! impl_partial_ord { + ($ttype:ident, $underlying_primitive:ident, ($($eq_type:ty),+)) => { + $( + impl PartialOrd<$eq_type> for $ttype { + fn partial_cmp(&self, other: &$eq_type) -> Option { + self.get().partial_cmp(&(*other as $underlying_primitive)) + } + } + )+ + }; +} + +pub type PositiveI32 = PositiveNum; +impl_into_primitive!(PositiveI32, i32); +impl_positive_num_from!(PositiveI32, i32, (u8, u16)); +impl_positive_num_try_from!(PositiveI32, i32, (i8, i16, i32)); +impl_partial_eq!(PositiveI32, i32, (i8, i16, i32)); +impl_partial_ord!(PositiveI32, i32, (i8, i16, i32)); + +pub type PositiveF64 = PositiveNum; +impl_into_primitive!(PositiveF64, f64); +impl_positive_num_from!(PositiveF64, f64, (u8, u16, u32)); +impl_positive_num_try_from!(PositiveF64, f64, (i8, i16, i32, f32, f64)); +impl_partial_eq!(PositiveF64, f64, (i8, i16, i32, f32, f64)); +impl_partial_ord!(PositiveF64, f64, (i8, i16, i32, f32, f64)); diff --git a/crates/types/src/math/vec2.rs b/crates/types/src/math/vec2.rs new file mode 100644 index 0000000..ff6b2e4 --- /dev/null +++ b/crates/types/src/math/vec2.rs @@ -0,0 +1,75 @@ +use manifold3d_sys::ManifoldVec2; +use std::ops::{Add, Sub}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Vec2 { + pub x: f64, + pub y: f64, +} + +impl Vec2 { + pub fn new(x: f64, y: f64) -> Self { + Self { x, y } + } +} + +impl From for Vec2 { + fn from(value: ManifoldVec2) -> Self { + Vec2 { + x: value.x, + y: value.y, + } + } +} + +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Vec2::new(self.x + rhs.x, self.y + rhs.y) + } +} + +impl Add for Vec2 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn add(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Vec2::new(self.x + value, self.y + value) + } +} + +impl Sub for Vec2 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Vec2::new(self.x - rhs.x, self.y - rhs.y) + } +} + +impl Sub for Vec2 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Vec2::new(self.x - value, self.y - value) + } +} + +#[cfg(feature = "nalgebra_interop")] +impl From> for Vec2 { + fn from(value: nalgebra::Vector2) -> Self { + Vec2 { + x: value.x, + y: value.y, + } + } +} diff --git a/crates/types/src/math/vec3.rs b/crates/types/src/math/vec3.rs new file mode 100644 index 0000000..739c1d6 --- /dev/null +++ b/crates/types/src/math/vec3.rs @@ -0,0 +1,78 @@ +use manifold3d_sys::ManifoldVec3; +use std::ops::{Add, Sub}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Vec3 { + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl Vec3 { + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self { x, y, z } + } +} + +impl From for Vec3 { + fn from(value: ManifoldVec3) -> Self { + Vec3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} + +impl Add for Vec3 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Vec3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + } +} + +impl Add for Vec3 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn add(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Vec3::new(self.x + value, self.y + value, self.z + value) + } +} + +impl Sub for Vec3 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Vec3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} + +impl Sub for Vec3 +where + f64: From, + T: num_traits::ToPrimitive, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + let value = f64::from(rhs); + Vec3::new(self.x - value, self.y - value, self.z - value) + } +} + +#[cfg(feature = "nalgebra_interop")] +impl From> for Vec3 { + fn from(value: nalgebra::Vector3) -> Self { + Vec3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} diff --git a/src/bounding_box.rs b/src/bounding_box.rs index df54842..4d0e6ca 100644 --- a/src/bounding_box.rs +++ b/src/bounding_box.rs @@ -1,4 +1,4 @@ -use crate::types::{Matrix4x3, Point3, Vec3}; +use crate::types::math::{Matrix4x3, Point3, Vec3}; use manifold3d_sys::{ manifold_alloc_box, manifold_box, manifold_box_center, manifold_box_contains_box, manifold_box_contains_pt, manifold_box_dimensions, manifold_box_does_overlap_box, @@ -13,10 +13,9 @@ pub struct BoundingBox(*mut ManifoldBox); impl BoundingBox { pub fn new(min_point: Point3, max_point: Point3) -> BoundingBox { - let manifold_box_ptr = unsafe { manifold_alloc_box() }; - unsafe { + let manifold_box_ptr = unsafe { manifold_box( - manifold_box_ptr as *mut c_void, + manifold_alloc_box() as *mut c_void, min_point.x, min_point.y, min_point.z, @@ -25,7 +24,7 @@ impl BoundingBox { max_point.z, ) }; - BoundingBox(manifold_box_ptr) + BoundingBox::from_ptr(manifold_box_ptr) } pub(crate) fn from_ptr(ptr: *mut ManifoldBox) -> BoundingBox { @@ -67,17 +66,16 @@ impl BoundingBox { } pub fn union(&self, other: &Self) -> Self { - let manifold_box_ptr = unsafe { manifold_alloc_box() }; - unsafe { manifold_box_union(manifold_box_ptr as *mut c_void, self.0, other.0) }; - BoundingBox(manifold_box_ptr) + let manifold_box_ptr = + unsafe { manifold_box_union(manifold_alloc_box() as *mut c_void, self.0, other.0) }; + BoundingBox::from_ptr(manifold_box_ptr) } pub fn transform(&self, matrix: impl Into) -> BoundingBox { let matrix = matrix.into(); - let manifold_box_ptr = unsafe { manifold_alloc_box() }; - unsafe { + let manifold_box_ptr = unsafe { manifold_box_transform( - manifold_box_ptr as *mut c_void, + manifold_alloc_box() as *mut c_void, self.0, matrix.rows[0].x, matrix.rows[0].y, @@ -93,43 +91,42 @@ impl BoundingBox { matrix.rows[3].z, ) }; - BoundingBox(manifold_box_ptr) + BoundingBox::from_ptr(manifold_box_ptr) } pub fn translate(&self, translation: impl Into) -> BoundingBox { let translation = translation.into(); - let manifold_box_ptr = unsafe { manifold_alloc_box() }; - unsafe { + let manifold_box_ptr = unsafe { manifold_box_translate( - manifold_box_ptr as *mut c_void, + manifold_alloc_box() as *mut c_void, self.0, translation.x, translation.y, translation.z, ) }; - BoundingBox(manifold_box_ptr) + BoundingBox::from_ptr(manifold_box_ptr) } pub fn multiply(&self, scale_factor: impl Into) -> BoundingBox { let scale = scale_factor.into(); - let manifold_box_ptr = unsafe { manifold_alloc_box() }; - unsafe { + let manifold_box_ptr = unsafe { manifold_box_mul( - manifold_box_ptr as *mut c_void, + manifold_alloc_box() as *mut c_void, self.0, scale.x, scale.y, scale.z, ) }; - BoundingBox(manifold_box_ptr) + BoundingBox::from_ptr(manifold_box_ptr) } pub fn overlaps_point(&self, point: impl Into) -> bool { let point = point.into(); - let manifold_box_ptr = unsafe { manifold_alloc_box() }; - unsafe { manifold_box_does_overlap_pt(manifold_box_ptr, point.x, point.y, point.z) == 1 } + unsafe { + manifold_box_does_overlap_pt(manifold_alloc_box(), point.x, point.y, point.z) == 1 + } } pub fn overlaps_bounding_box(&self, other: &BoundingBox) -> bool { diff --git a/src/error.rs b/src/error.rs index 2364a75..57abfbe 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,14 +1,5 @@ use crate::manifold::Manifold; -use manifold3d_sys::{ - manifold_status, ManifoldError, ManifoldError_MANIFOLD_FACE_ID_WRONG_LENGTH, - ManifoldError_MANIFOLD_INVALID_CONSTRUCTION, ManifoldError_MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS, - ManifoldError_MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS, - ManifoldError_MANIFOLD_MISSING_POSITION_PROPERTIES, ManifoldError_MANIFOLD_NON_FINITE_VERTEX, - ManifoldError_MANIFOLD_NOT_MANIFOLD, ManifoldError_MANIFOLD_NO_ERROR, - ManifoldError_MANIFOLD_PROPERTIES_WRONG_LENGTH, ManifoldError_MANIFOLD_RUN_INDEX_WRONG_LENGTH, - ManifoldError_MANIFOLD_TRANSFORM_WRONG_LENGTH, - ManifoldError_MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS, -}; +use manifold3d_sys::{manifold_status, ManifoldError}; #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] #[non_exhaustive] @@ -28,24 +19,38 @@ pub enum Error { Unknown(u32), } -impl From for Error { - fn from(value: u32) -> Self { - #[allow(non_upper_case_globals)] +impl From for Error { + fn from(value: ManifoldError) -> Self { + // #[allow(non_upper_case_globals)] match value { - ManifoldError_MANIFOLD_NO_ERROR => Error::NoError, - ManifoldError_MANIFOLD_NON_FINITE_VERTEX => Error::NonFiniteVertex, - ManifoldError_MANIFOLD_NOT_MANIFOLD => Error::NotManifold, - ManifoldError_MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS => Error::VertexIndexOutOfBounds, - ManifoldError_MANIFOLD_PROPERTIES_WRONG_LENGTH => Error::PropertiesWrongLength, - ManifoldError_MANIFOLD_MISSING_POSITION_PROPERTIES => Error::MissingPositionProperties, - ManifoldError_MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS => { + manifold3d_sys::ManifoldError_MANIFOLD_NO_ERROR => Error::NoError, + manifold3d_sys::ManifoldError_MANIFOLD_NON_FINITE_VERTEX => Error::NonFiniteVertex, + manifold3d_sys::ManifoldError_MANIFOLD_NOT_MANIFOLD => Error::NotManifold, + manifold3d_sys::ManifoldError_MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS => { + Error::VertexIndexOutOfBounds + } + manifold3d_sys::ManifoldError_MANIFOLD_PROPERTIES_WRONG_LENGTH => { + Error::PropertiesWrongLength + } + manifold3d_sys::ManifoldError_MANIFOLD_MISSING_POSITION_PROPERTIES => { + Error::MissingPositionProperties + } + manifold3d_sys::ManifoldError_MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS => { Error::MergeVectorsDifferentLengths } - ManifoldError_MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS => Error::MergeIndexOutOfBounds, - ManifoldError_MANIFOLD_TRANSFORM_WRONG_LENGTH => Error::TransformWrongLength, - ManifoldError_MANIFOLD_RUN_INDEX_WRONG_LENGTH => Error::RunIndexWrongLength, - ManifoldError_MANIFOLD_FACE_ID_WRONG_LENGTH => Error::FaceIdWrongLength, - ManifoldError_MANIFOLD_INVALID_CONSTRUCTION => Error::InvalidConstruction, + manifold3d_sys::ManifoldError_MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS => { + Error::MergeIndexOutOfBounds + } + manifold3d_sys::ManifoldError_MANIFOLD_TRANSFORM_WRONG_LENGTH => { + Error::TransformWrongLength + } + manifold3d_sys::ManifoldError_MANIFOLD_RUN_INDEX_WRONG_LENGTH => { + Error::RunIndexWrongLength + } + manifold3d_sys::ManifoldError_MANIFOLD_FACE_ID_WRONG_LENGTH => Error::FaceIdWrongLength, + manifold3d_sys::ManifoldError_MANIFOLD_INVALID_CONSTRUCTION => { + Error::InvalidConstruction + } value => Error::Unknown(value), } } @@ -69,17 +74,14 @@ impl ManifoldErrorExt for ManifoldError { } mod tests { - use crate::error::Error; - use manifold3d_sys::{ - ManifoldError_MANIFOLD_NON_FINITE_VERTEX, ManifoldError_MANIFOLD_NO_ERROR, - }; + use crate::Error; #[test] fn test_error_from_u32() { // Checks whether the error discrimination works at all - assert_eq!(Error::from(ManifoldError_MANIFOLD_NO_ERROR), Error::NoError); + assert_eq!(Error::from(manifold3d_sys::ManifoldError_MANIFOLD_NO_ERROR), Error::NoError); assert_eq!( - Error::from(ManifoldError_MANIFOLD_NON_FINITE_VERTEX), + Error::from(manifold3d_sys::ManifoldError_MANIFOLD_NON_FINITE_VERTEX), Error::NonFiniteVertex ); } diff --git a/src/lib.rs b/src/lib.rs index 9dccb8d..9d2cd45 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,13 +7,14 @@ mod mesh_gl; mod polygons; mod quality; mod simple_polygon; -mod types; pub use bounding_box::*; pub use error::*; pub use manifold::*; +pub use manifold3d_macros as macros; +pub use manifold3d_sys as sys; +pub use manifold3d_types as types; pub use mesh_gl::*; pub use polygons::*; pub use quality::*; pub use simple_polygon::*; -pub use types::*; diff --git a/src/manifold.rs b/src/manifold.rs index 2f75af1..9951ea4 100644 --- a/src/manifold.rs +++ b/src/manifold.rs @@ -1,14 +1,24 @@ use crate::bounding_box::BoundingBox; use crate::error::{check_error, Error}; use crate::mesh_gl::MeshGL; -use crate::types::{PositiveF64, PositiveI32, Vec3}; +use crate::types::manifold::vertex; +use crate::types::math::{PositiveF64, PositiveI32, Vec3}; use manifold3d_sys::{ - manifold_alloc_box, manifold_alloc_manifold, manifold_alloc_meshgl, manifold_bounding_box, - manifold_copy, manifold_cube, manifold_cylinder, manifold_delete_manifold, manifold_empty, - manifold_get_meshgl, manifold_is_empty, manifold_num_edge, manifold_num_tri, manifold_num_vert, - manifold_of_meshgl, manifold_sphere, manifold_tetrahedron, ManifoldManifold, + manifold_alloc_box, manifold_alloc_manifold, manifold_alloc_manifold_vec, + manifold_alloc_meshgl, manifold_batch_boolean, manifold_boolean, manifold_bounding_box, + manifold_copy, manifold_cube, manifold_cylinder, manifold_delete_manifold, manifold_difference, + manifold_empty, manifold_get_meshgl, manifold_intersection, manifold_is_empty, + manifold_manifold_vec, manifold_manifold_vec_set, manifold_mirror, manifold_num_edge, + manifold_num_tri, manifold_num_vert, manifold_of_meshgl, manifold_refine, + manifold_refine_to_length, manifold_refine_to_tolerance, manifold_scale, + manifold_smooth_by_normals, manifold_smooth_out, manifold_sphere, manifold_split, + manifold_split_by_plane, manifold_tetrahedron, manifold_translate, manifold_trim_by_plane, + manifold_union, manifold_warp, ManifoldManifold, ManifoldOpType, }; +use manifold3d_types::math::{NonNegativeF64, NonNegativeI32, NormalizedAngle}; use std::os::raw::{c_int, c_void}; +use std::pin::Pin; +use thiserror::Error; pub struct Manifold(*mut ManifoldManifold); @@ -30,8 +40,8 @@ impl Manifold { /// /// # Examples /// ``` + /// use manifold3d::types::math::PositiveF64; /// use manifold3d::Manifold; - /// use manifold3d::PositiveF64; /// /// // A cuboid of size 1x2x3, touching the origin in the first octant. /// let cuboid = Manifold::new_cuboid(1u8, 2u16, 3u32, false); @@ -113,8 +123,8 @@ impl Manifold { /// /// # Examples /// ``` + /// use manifold3d::types::math::Vec3; /// use manifold3d::Manifold; - /// use manifold3d::Vec3; /// /// // A cuboid of size 1x2x3, touching the origin in the first octant. /// let cuboid = unsafe { @@ -183,7 +193,7 @@ impl Manifold { manifold_cylinder( manifold_alloc_manifold() as *mut c_void, height.into(), - bottom_radius.into().into(), + bottom_radius.into(), top_radius.into(), circular_segments.into(), origin_at_center as c_int, @@ -231,6 +241,251 @@ impl Manifold { self.0 } + // Boolean Operations + pub fn boolean(&self, other: &Manifold, operation: BooleanOperation) -> Manifold { + let manifold_ptr = unsafe { + manifold_boolean( + manifold_alloc_manifold() as *mut c_void, + self.0, + other.0, + operation.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn batch_boolean(&self, others: &[Manifold], operation: BooleanOperation) -> Manifold { + if others.is_empty() { + return self.clone(); + } + // Check includes self in vec + if others.len() >= usize::MAX { + panic!("Batch operation exceeds maximum allowed count of elements") + } + + let batch_vec_ptr = unsafe { + manifold_manifold_vec( + manifold_alloc_manifold_vec() as *mut c_void, + others.len() + 1, + ) + }; + let mut batch_vec_index = 0; + unsafe { manifold_manifold_vec_set(batch_vec_ptr, batch_vec_index, self.0) }; + batch_vec_index += 1; + + for other in others { + unsafe { manifold_manifold_vec_set(batch_vec_ptr, batch_vec_index, other.0) }; + batch_vec_index += 1; + } + let manifold_ptr = unsafe { + manifold_batch_boolean( + manifold_alloc_manifold() as *mut c_void, + batch_vec_ptr, + operation.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn union(&self, other: &Manifold) -> Manifold { + let manifold_ptr = + unsafe { manifold_union(manifold_alloc_manifold() as *mut c_void, self.0, other.0) }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn difference(&self, other: &Manifold) -> Manifold { + let manifold_ptr = unsafe { + manifold_difference(manifold_alloc_manifold() as *mut c_void, self.0, other.0) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn intersection(&self, other: &Manifold) -> Manifold { + let manifold_ptr = unsafe { + manifold_intersection(manifold_alloc_manifold() as *mut c_void, self.0, other.0) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn split(&self, other: &Manifold) -> (Manifold, Manifold) { + let manifold_pair = unsafe { + manifold_split( + manifold_alloc_manifold() as *mut c_void, + manifold_alloc_manifold() as *mut c_void, + self.0, + other.0, + ) + }; + ( + Manifold::from_ptr(manifold_pair.first), + Manifold::from_ptr(manifold_pair.second), + ) + } + + pub fn split_by_plane(&self, plane: Plane) -> (Manifold, Manifold) { + let manifold_pair = unsafe { + manifold_split_by_plane( + manifold_alloc_manifold() as *mut c_void, + manifold_alloc_manifold() as *mut c_void, + self.0, + plane.x_normal, + plane.y_normal, + plane.z_normal, + plane.offset, + ) + }; + ( + Manifold::from_ptr(manifold_pair.first), + Manifold::from_ptr(manifold_pair.second), + ) + } + + pub fn trim_by_plane(&self, plane: Plane) -> Manifold { + let manifold_ptr = unsafe { + manifold_trim_by_plane( + manifold_alloc_manifold() as *mut c_void, + self.0, + plane.x_normal, + plane.y_normal, + plane.z_normal, + plane.offset, + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + // Transformations + + pub fn translate(&self, translation: impl Into) -> Manifold { + let translation = translation.into(); + let manifold_ptr = unsafe { + manifold_translate( + manifold_alloc_manifold() as *mut c_void, + self.0, + translation.x, + translation.y, + translation.z, + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn rotate(&self, rotation: impl Into) -> Manifold { + let rotation = rotation.into(); + let manifold_ptr = unsafe { + manifold_translate( + manifold_alloc_manifold() as *mut c_void, + self.0, + rotation.x, + rotation.y, + rotation.z, + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn scale(&self, scale: impl Into) -> Manifold { + let scale = scale.into(); + let manifold_ptr = unsafe { + manifold_scale( + manifold_alloc_manifold() as *mut c_void, + self.0, + scale.x, + scale.y, + scale.z, + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn mirror(&self, scale: impl Into) -> Manifold { + let scale = scale.into(); + let manifold_ptr = unsafe { + manifold_mirror( + manifold_alloc_manifold() as *mut c_void, + self.0, + scale.x, + scale.y, + scale.z, + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn warp(&self, warp: Pin<&impl vertex::Warp>) -> Manifold { + let warp_ptr = &raw const *warp; + let manifold_ptr = unsafe { + manifold_warp( + manifold_alloc_manifold() as *mut c_void, + self.0, + Some(warp.extern_c_warp_fn()), + warp_ptr as *mut c_void, + ) + }; + let _ = warp; + Manifold::from_ptr(manifold_ptr) + } + + pub fn smooth_by_normals(&self, vertex_normal_property_index: NonNegativeI32) -> Manifold { + let manifold_ptr = unsafe { + manifold_smooth_by_normals( + manifold_alloc_manifold() as *mut c_void, + self.0, + vertex_normal_property_index.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn smooth_out( + &self, + min_sharp_angle: NormalizedAngle, + min_smoothness: MinimumSmoothness, + ) -> Manifold { + let manifold_ptr = unsafe { + manifold_smooth_out( + manifold_alloc_manifold() as *mut c_void, + self.0, + min_sharp_angle.into(), + min_smoothness.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + fn refine_via_edge_splits(&self, edge_split_count: EdgeSplitCount) -> Manifold { + let manifold_ptr = unsafe { + manifold_refine( + manifold_alloc_manifold() as *mut c_void, + self.0, + edge_split_count.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn refine_to_edge_length(&self, edge_length: NonNegativeF64) -> Manifold { + let manifold_ptr = unsafe { + manifold_refine_to_length( + manifold_alloc_manifold() as *mut c_void, + self.0, + edge_length.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + + pub fn refine_to_tolerance(&self, tolerance: NonNegativeF64) -> Manifold { + let manifold_ptr = unsafe { + manifold_refine_to_tolerance( + manifold_alloc_manifold() as *mut c_void, + self.0, + tolerance.into(), + ) + }; + Manifold::from_ptr(manifold_ptr) + } + pub fn is_empty(&self) -> bool { unsafe { manifold_is_empty(self.0) == 1 } } @@ -247,7 +502,7 @@ impl Manifold { unsafe { manifold_num_tri(self.0) } } - pub fn get_mesh(&self) -> MeshGL { + pub fn mesh(&self) -> MeshGL { let mesh_gl_ptr = unsafe { manifold_get_meshgl(manifold_alloc_meshgl() as *mut c_void, self.0) }; MeshGL::from_ptr(mesh_gl_ptr) @@ -285,3 +540,112 @@ impl Drop for Manifold { } } } + +pub enum BooleanOperation { + Add, + Subtract, + Intersect, +} + +impl From for ManifoldOpType { + fn from(val: BooleanOperation) -> Self { + match val { + BooleanOperation::Add => manifold3d_sys::ManifoldOpType_MANIFOLD_ADD, + BooleanOperation::Subtract => manifold3d_sys::ManifoldOpType_MANIFOLD_SUBTRACT, + BooleanOperation::Intersect => manifold3d_sys::ManifoldOpType_MANIFOLD_INTERSECT, + } + } +} + +#[derive(Error, Debug)] +pub enum MinimumSmoothnessError { + #[error( + "Minimum smoothness value must be between {minimum} and {maximum}. {actual} was provided" + )] + OutOfBounds { + minimum: f64, + maximum: f64, + actual: f64, + }, +} + +pub struct MinimumSmoothness(f64); + +impl MinimumSmoothness { + const MINIMUM: f64 = 0.0; + const MAXIMUM: f64 = 1.0; + + pub fn new(smoothness: impl Into) -> Result { + let smoothness = smoothness.into(); + if !(MinimumSmoothness::MINIMUM..=MinimumSmoothness::MAXIMUM).contains(&smoothness) { + return Err(MinimumSmoothnessError::OutOfBounds { + minimum: MinimumSmoothness::MINIMUM, + maximum: MinimumSmoothness::MAXIMUM, + actual: smoothness, + }); + } + Ok(Self(smoothness)) + } + + pub fn get(&self) -> f64 { + self.0 + } +} + +impl From for f64 { + fn from(val: MinimumSmoothness) -> Self { + val.get() + } +} + +#[derive(Error, Debug)] +pub enum EdgeSplitCountError { + #[error("Edge split count must be at least {minimum}. {actual} was provided")] + TooSmall { minimum: i32, actual: i32 }, +} + +pub struct EdgeSplitCount(PositiveI32); + +impl EdgeSplitCount { + const MINIMUM_SPLIT_COUNT: i32 = 2; + + pub fn new(num: impl Into) -> Result { + let num = num.into(); + if num < EdgeSplitCount::MINIMUM_SPLIT_COUNT { + return Err(EdgeSplitCountError::TooSmall { + minimum: EdgeSplitCount::MINIMUM_SPLIT_COUNT, + actual: num.get(), + }); + } + Ok(Self(num)) + } + + pub fn get(&self) -> i32 { + self.0.get() + } +} + +impl From for i32 { + fn from(val: EdgeSplitCount) -> Self { + val.get() + } +} + +pub struct Plane { + pub x_normal: f64, + pub y_normal: f64, + pub z_normal: f64, + pub offset: f64, +} + +impl Plane { + #[must_use] + pub fn new(x_normal: f64, y_normal: f64, z_normal: f64, offset: f64) -> Self { + Self { + x_normal, + y_normal, + z_normal, + offset, + } + } +} diff --git a/src/polygons.rs b/src/polygons.rs index 1ee23d5..9107c35 100644 --- a/src/polygons.rs +++ b/src/polygons.rs @@ -1,7 +1,7 @@ use crate::error::{check_error, Error}; use crate::manifold::Manifold; use crate::simple_polygon::SimplePolygon; -use crate::types::{NormalizedAngle, PositiveF64, PositiveI32, Vec2}; +use crate::types::math::{NormalizedAngle, PositiveF64, PositiveI32, Vec2}; use manifold3d_sys::{ manifold_alloc_manifold, manifold_alloc_polygons, manifold_alloc_simple_polygon, manifold_delete_polygons, manifold_extrude, manifold_polygons, manifold_polygons_get_simple, diff --git a/src/quality.rs b/src/quality.rs index f438348..4c59be5 100644 --- a/src/quality.rs +++ b/src/quality.rs @@ -1,41 +1,41 @@ -use crate::types::{NormalizedAngle, PositiveF64, PositiveI32}; +use crate::types::math::{NormalizedAngle, PositiveF64, PositiveI32}; use manifold3d_sys::{ manifold_get_circular_segments, manifold_reset_to_circular_defaults, manifold_set_circular_segments, manifold_set_min_circular_angle, manifold_set_min_circular_edge_length, }; -use std::num::NonZeroI32; pub fn set_min_circular_angle(angle: NormalizedAngle) { unsafe { manifold_set_min_circular_angle(angle.as_degrees()) } } -pub fn set_min_circular_angle_unchecked(angle: f64) { - unsafe { manifold_set_min_circular_angle(angle) } +pub unsafe fn set_min_circular_angle_unchecked(angle: f64) { + manifold_set_min_circular_angle(angle) } pub fn set_min_circular_edge_length(length: PositiveF64) { unsafe { manifold_set_min_circular_edge_length(length.get()) } } -pub fn set_min_circular_edge_length_unchecked(length: f64) { - unsafe { manifold_set_min_circular_edge_length(length) } +pub unsafe fn set_min_circular_edge_length_unchecked(length: f64) { + manifold_set_min_circular_edge_length(length) } pub fn set_circular_segments(segments: PositiveI32) { unsafe { manifold_set_circular_segments(segments.get()) } } -pub fn set_circular_segments_unchecked(segments: i32) { - unsafe { manifold_set_circular_segments(segments) } +pub unsafe fn set_circular_segments_unchecked(segments: i32) { + manifold_set_circular_segments(segments) } -pub fn get_circular_segments(radius: PositiveF64) -> NonZeroI32 { - unsafe { NonZeroI32::new_unchecked(manifold_get_circular_segments(radius.get())) } +pub fn get_circular_segments(radius: PositiveF64) -> PositiveI32 { + let segments = unsafe { manifold_get_circular_segments(radius.get()) }; + PositiveI32::new(segments).unwrap() } -pub fn get_circular_segments_unchecked(radius: f64) -> i32 { - unsafe { manifold_get_circular_segments(radius) } +pub unsafe fn get_circular_segments_unchecked(radius: f64) -> i32 { + manifold_get_circular_segments(radius) } pub fn reset_to_circular_defaults() { @@ -45,7 +45,7 @@ pub fn reset_to_circular_defaults() { #[cfg(test)] mod tests { use crate::quality::*; - use crate::types::{NormalizedAngle, PositiveF64, PositiveI32}; + use crate::types::math::{NormalizedAngle, PositiveF64, PositiveI32}; #[test] fn test_set_and_get_circular_segments() { diff --git a/src/simple_polygon.rs b/src/simple_polygon.rs index 1bc3fc7..7f985bd 100644 --- a/src/simple_polygon.rs +++ b/src/simple_polygon.rs @@ -1,5 +1,9 @@ -use crate::types::{Point2, PositiveI32}; -use manifold3d_sys::{manifold_alloc_simple_polygon, manifold_delete_simple_polygon, manifold_simple_polygon, manifold_simple_polygon_get_point, manifold_simple_polygon_length, ManifoldSimplePolygon, ManifoldVec2}; +use crate::types::math::{Point2, PositiveI32}; +use manifold3d_sys::{ + manifold_alloc_simple_polygon, manifold_delete_simple_polygon, manifold_simple_polygon, + manifold_simple_polygon_get_point, manifold_simple_polygon_length, ManifoldSimplePolygon, + ManifoldVec2, +}; use std::os::raw::{c_int, c_void}; pub struct SimplePolygon(*mut ManifoldSimplePolygon); @@ -46,4 +50,4 @@ impl Drop for SimplePolygon { fn drop(&mut self) { unsafe { manifold_delete_simple_polygon(self.0) } } -} \ No newline at end of file +} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 5faf1f6..0000000 --- a/src/types.rs +++ /dev/null @@ -1,282 +0,0 @@ -use manifold3d_sys::{ManifoldVec2, ManifoldVec3}; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum PositiveNumError { - #[error("the value is not positive")] - NonPositiveValue, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Vec2 { - pub x: f64, - pub y: f64, -} - -impl From for Vec2 { - fn from(value: ManifoldVec2) -> Self { - Vec2 { - x: value.x, - y: value.y, - } - } -} - -#[cfg(feature = "nalgebra_interop")] -impl From> for Vec2 { - fn from(value: nalgebra::Vector2) -> Self { - Vec2 { - x: value.x, - y: value.y, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Vec3 { - pub x: f64, - pub y: f64, - pub z: f64, -} - -impl From for Vec3 { - fn from(value: ManifoldVec3) -> Self { - Vec3 { - x: value.x, - y: value.y, - z: value.z, - } - } -} - -#[cfg(feature = "nalgebra_interop")] -impl From> for Vec3 { - fn from(value: nalgebra::Vector3) -> Self { - Vec3 { - x: value.x, - y: value.y, - z: value.z, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Point2 { - pub x: f64, - pub y: f64, -} - -impl From for Point2 { - fn from(value: ManifoldVec2) -> Self { - Point2 { - x: value.x, - y: value.y, - } - } -} - -impl From for ManifoldVec2 { - fn from(value: Point2) -> Self { - ManifoldVec2 { - x: value.x, - y: value.y, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Point3 { - pub x: f64, - pub y: f64, - pub z: f64, -} - -impl From for Point3 { - fn from(value: ManifoldVec3) -> Self { - Point3 { - x: value.x, - y: value.y, - z: value.z, - } - } -} - -#[cfg(feature = "nalgebra_interop")] -impl From> for Point3 { - fn from(value: nalgebra::Point3) -> Self { - Point3 { - x: value.x, - y: value.y, - z: value.z, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Matrix4x3 { - pub rows: [Vec3; 4], -} - -#[cfg(feature = "nalgebra_interop")] -impl From> for Matrix4x3 { - fn from(matrix: nalgebra::Matrix4x3) -> Self { - Matrix4x3 { - rows: [ - Vec3 { - x: matrix.m11, - y: matrix.m12, - z: matrix.m13, - }, - Vec3 { - x: matrix.m21, - y: matrix.m22, - z: matrix.m23, - }, - Vec3 { - x: matrix.m31, - y: matrix.m32, - z: matrix.m33, - }, - Vec3 { - x: matrix.m41, - y: matrix.m42, - z: matrix.m43, - }, - ], - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct PositiveNum(T); - -impl PositiveNum { - pub fn new(value: T) -> Result { - if !Self::is_valid(value) { - return Err(PositiveNumError::NonPositiveValue); - } - Ok(PositiveNum(value)) - } - - #[inline(always)] - fn is_valid(value: T) -> bool { - value > T::zero() - } -} - -impl PositiveNum { - pub fn get(&self) -> T { - self.0 - } -} - -macro_rules! impl_positive_num_from { - ($ttype:ident, $underlying_primitive:ident, ($($from_type:ty),+)) => { - $( - impl From<$from_type> for $ttype { - fn from(value: $from_type) -> Self { - PositiveNum($underlying_primitive::from(value)) - } - } - )+ - }; -} - -macro_rules! impl_positive_num_try_from { - ($ttype:ident, $underlying_primitive:ident, ($($from_type:ty),+)) => { - $( - impl TryFrom<$from_type> for $ttype { - type Error = PositiveNumError; - - fn try_from(value: $from_type) -> Result { - $ttype::new($underlying_primitive::from(value)) - } - } - )+ - }; -} - -macro_rules! impl_into_primitive { - ($ttype:ident, $underlying_primitive:ident) => { - #[allow(clippy::from_over_into)] - impl Into<$underlying_primitive> for $ttype { - fn into(self) -> $underlying_primitive { - self.get() - } - } - }; -} - -pub type PositiveI32 = PositiveNum; -impl_into_primitive!(PositiveI32, i32); -impl_positive_num_from!(PositiveI32, i32, (u8, u16)); -impl_positive_num_try_from!(PositiveI32, i32, (i8, i16, i32)); - -pub type PositiveF64 = PositiveNum; -impl_into_primitive!(PositiveF64, f64); -impl_positive_num_from!(PositiveF64, f64, (u8, u16, u32)); -impl_positive_num_try_from!(PositiveF64, f64, (i8, i16, i32, f32, f64)); - -/// Represents an angle, measured in degrees, constrained to the range [-360.0, 360.0]. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct NormalizedAngle(f64); - -impl NormalizedAngle { - pub fn from_degrees(value: T) -> Self - where - T: Into, - { - NormalizedAngle(Self::normalize(value)) - } - - pub fn as_degrees(&self) -> f64 { - self.0 - } - - fn normalize(value: impl Into) -> f64 { - let mut value = value.into() % 360.0; - if value < 0.0 { - value = 360.0 - value; - } - value - } -} - -impl From> for NormalizedAngle -where - T: num_traits::Num + PartialOrd + Copy, - f64: From, -{ - fn from(value: PositiveNum) -> Self { - NormalizedAngle(f64::from(value.get()) % 360.0) - } -} - -impl Add for NormalizedAngle { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self::from_degrees(self.0 + rhs.0) - } -} - -impl AddAssign for NormalizedAngle { - fn add_assign(&mut self, rhs: Self) { - self.0 = Self::normalize(self.0 + rhs.0); - } -} - -impl Sub for NormalizedAngle { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self::from_degrees(self.0 - rhs.0) - } -} - -impl SubAssign for NormalizedAngle { - fn sub_assign(&mut self, rhs: Self) { - self.0 = Self::normalize(self.0 - rhs.0); - } -}