Merge pull request #374 from hannobraun/math

Extract math code into dedicated crate
This commit is contained in:
Hanno Braun 2022-03-17 17:07:21 +01:00 committed by GitHub
commit 15294c2ca2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 318 additions and 239 deletions

15
Cargo.lock generated
View File

@ -629,15 +629,14 @@ dependencies = [
"approx 0.5.1", "approx 0.5.1",
"bytemuck", "bytemuck",
"clap", "clap",
"decorum",
"figment", "figment",
"fj", "fj",
"fj-math",
"futures", "futures",
"libloading", "libloading",
"map-macro", "map-macro",
"nalgebra", "nalgebra",
"notify", "notify",
"num-traits",
"parking_lot 0.12.0", "parking_lot 0.12.0",
"parry2d-f64", "parry2d-f64",
"parry3d-f64", "parry3d-f64",
@ -652,6 +651,18 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "fj-math"
version = "0.5.0"
dependencies = [
"approx 0.5.1",
"decorum",
"nalgebra",
"num-traits",
"parry2d-f64",
"parry3d-f64",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.22" version = "1.0.22"

View File

@ -3,6 +3,7 @@ resolver = "2"
members = [ members = [
"fj", "fj",
"fj-app", "fj-app",
"fj-math",
"models/cuboid", "models/cuboid",
"models/group", "models/group",
@ -11,4 +12,7 @@ members = [
"release-operator", "release-operator",
] ]
default-members = ["fj-app"] default-members = [
"fj-app",
"fj-math",
]

View File

@ -4,7 +4,7 @@ version = "0.5.0"
edition = "2021" edition = "2021"
description = "The world needs another CAD program." description = "The world needs another CAD program."
readme = "README.md" readme = "../README.md"
repository = "https://github.com/hannobraun/fornjot" repository = "https://github.com/hannobraun/fornjot"
license = "0BSD" license = "0BSD"
keywords = ["cad", "programmatic", "code-cad"] keywords = ["cad", "programmatic", "code-cad"]
@ -15,13 +15,11 @@ categories = ["mathematics", "rendering"]
anyhow = "1.0.56" anyhow = "1.0.56"
approx = "0.5.1" approx = "0.5.1"
bytemuck = "1.8.0" bytemuck = "1.8.0"
decorum = "0.3.1"
futures = "0.3.21" futures = "0.3.21"
libloading = "0.7.2" libloading = "0.7.2"
map-macro = "0.2.0" map-macro = "0.2.0"
nalgebra = "0.30.0" nalgebra = "0.30.0"
notify = "5.0.0-pre.14" notify = "5.0.0-pre.14"
num-traits = "0.2.14"
parking_lot = "0.12.0" parking_lot = "0.12.0"
parry2d-f64 = "0.8.0" parry2d-f64 = "0.8.0"
parry3d-f64 = "0.8.0" parry3d-f64 = "0.8.0"
@ -45,6 +43,10 @@ features = ["env", "toml"]
version = "0.5.0" version = "0.5.0"
path = "../fj" path = "../fj"
[dependencies.fj-math]
version = "0.5.0"
path = "../fj-math"
[dependencies.serde] [dependencies.serde]
version = "1.0.136" version = "1.0.136"
features = ["derive"] features = ["derive"]

View File

@ -1,13 +1,11 @@
use std::f64::consts::FRAC_PI_2; use std::f64::consts::FRAC_PI_2;
use fj_math::{Aabb, Scalar, Triangle};
use nalgebra::{Point, TAffine, Transform, Translation, Vector}; use nalgebra::{Point, TAffine, Transform, Translation, Vector};
use parry3d_f64::query::{Ray, RayCast as _}; use parry3d_f64::query::{Ray, RayCast as _};
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use crate::{ use crate::window::Window;
math::{Aabb, Scalar, Triangle},
window::Window,
};
/// The camera abstraction /// The camera abstraction
/// ///

View File

@ -1,13 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use fj_math::Aabb;
use wgpu::util::StagingBelt; use wgpu::util::StagingBelt;
use wgpu_glyph::{ use wgpu_glyph::{
ab_glyph::{FontArc, InvalidFont}, ab_glyph::{FontArc, InvalidFont},
GlyphBrush, GlyphBrushBuilder, Section, Text, GlyphBrush, GlyphBrushBuilder, Section, Text,
}; };
use crate::math::Aabb;
use super::draw_config::DrawConfig; use super::draw_config::DrawConfig;
#[derive(Debug)] #[derive(Debug)]
@ -70,7 +69,7 @@ impl ConfigUi {
} }
/* Render size of model bounding box */ /* Render size of model bounding box */
let bbsize = aabb.size().components(); let bbsize = aabb.size().components;
let info = format!( let info = format!(
"Model bounding box size: {:0.1} {:0.1} {:0.1}", "Model bounding box size: {:0.1} {:0.1} {:0.1}",
bbsize[0].into_f32(), bbsize[0].into_f32(),

View File

@ -1,6 +1,6 @@
use crate::math::Aabb;
use std::convert::TryInto; use std::convert::TryInto;
use fj_math::Aabb;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use super::vertices::{Vertex, Vertices}; use super::vertices::{Vertex, Vertices};

View File

@ -1,12 +1,13 @@
use std::{io, mem::size_of}; use std::{io, mem::size_of};
use fj_math::{Aabb, Point};
use thiserror::Error; use thiserror::Error;
use tracing::debug; use tracing::debug;
use wgpu::util::DeviceExt as _; use wgpu::util::DeviceExt as _;
use wgpu_glyph::ab_glyph::InvalidFont; use wgpu_glyph::ab_glyph::InvalidFont;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use crate::{camera::Camera, math::Aabb, math::Point, window::Window}; use crate::{camera::Camera, window::Window};
use super::{ use super::{
config_ui::ConfigUi, draw_config::DrawConfig, drawables::Drawables, config_ui::ConfigUi, draw_config::DrawConfig, drawables::Drawables,

View File

@ -1,9 +1,9 @@
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use fj_math::Triangle;
use nalgebra::{vector, Point}; use nalgebra::{vector, Point};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
math::Triangle,
mesh::{Index, MeshMaker}, mesh::{Index, MeshMaker},
}; };

View File

@ -1,5 +1,6 @@
use std::time::Instant; use std::time::Instant;
use fj_math::Triangle;
use winit::{ use winit::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
event::{ event::{
@ -10,7 +11,6 @@ use winit::{
use crate::{ use crate::{
camera::{Camera, FocusPoint}, camera::{Camera, FocusPoint},
math::Triangle,
window::Window, window::Window,
}; };

View File

@ -1,12 +1,11 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use fj_math::{Point, Scalar, Segment};
kernel::topology::{
use crate::kernel::topology::{
edges::{Cycle, Edge}, edges::{Cycle, Edge},
faces::Face, faces::Face,
vertices::Vertex, vertices::Vertex,
},
math::{Point, Scalar, Segment},
}; };
/// An approximation of an edge, multiple edges, or a face /// An approximation of an edge, multiple edges, or a face
@ -132,15 +131,13 @@ fn approximate_edge(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use fj_math::{Point, Scalar, Segment};
use map_macro::set; use map_macro::set;
use crate::{ use crate::kernel::{
kernel::{
geometry::Surface, geometry::Surface,
shape::Shape, shape::Shape,
topology::{edges::Cycle, faces::Face, vertices::Vertex}, topology::{edges::Cycle, faces::Face, vertices::Vertex},
},
math::{Point, Scalar, Segment},
}; };
use super::{approximate_edge, Approximation}; use super::{approximate_edge, Approximation};

View File

@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use fj_math::{Scalar, Transform, Triangle, Vector};
kernel::{
use crate::kernel::{
geometry::{surfaces::Swept, Surface}, geometry::{surfaces::Swept, Surface},
shape::{Handle, Shape}, shape::{Handle, Shape},
topology::{ topology::{
@ -9,8 +10,6 @@ use crate::{
faces::Face, faces::Face,
vertices::Vertex, vertices::Vertex,
}, },
},
math::{Scalar, Transform, Triangle, Vector},
}; };
use super::approximation::Approximation; use super::approximation::Approximation;
@ -322,13 +321,12 @@ impl Relation {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use fj_math::{Point, Scalar, Vector};
kernel::{
use crate::kernel::{
geometry::{surfaces::Swept, Surface}, geometry::{surfaces::Swept, Surface},
shape::{Handle, Shape}, shape::{Handle, Shape},
topology::{edges::Cycle, faces::Face, vertices::Vertex}, topology::{edges::Cycle, faces::Face, vertices::Vertex},
},
math::{Point, Scalar, Vector},
}; };
use super::sweep_shape; use super::sweep_shape;

View File

@ -1,7 +1,8 @@
use fj_math::Scalar;
use parry2d_f64::utils::point_in_triangle::{corner_direction, Orientation}; use parry2d_f64::utils::point_in_triangle::{corner_direction, Orientation};
use spade::HasPosition; use spade::HasPosition;
use crate::{kernel::geometry, math::Scalar}; use crate::kernel::geometry;
/// Create a Delaunay triangulation of all points /// Create a Delaunay triangulation of all points
pub fn triangulate( pub fn triangulate(

View File

@ -1,6 +1,6 @@
use std::f64::consts::PI; use std::f64::consts::PI;
use crate::math::{Point, Scalar, Transform, Vector}; use fj_math::{Point, Scalar, Transform, Vector};
/// A circle /// A circle
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
@ -109,7 +109,7 @@ impl Circle {
mod tests { mod tests {
use std::f64::consts::{FRAC_PI_2, PI}; use std::f64::consts::{FRAC_PI_2, PI};
use crate::math::{Point, Scalar, Vector}; use fj_math::{Point, Scalar, Vector};
use super::Circle; use super::Circle;

View File

@ -1,4 +1,4 @@
use crate::math::{Point, Transform, Vector}; use fj_math::{Point, Transform, Vector};
/// A line, defined by a point and a vector /// A line, defined by a point and a vector
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
@ -83,11 +83,10 @@ mod tests {
use std::f64::consts::FRAC_PI_2; use std::f64::consts::FRAC_PI_2;
use approx::assert_abs_diff_eq; use approx::assert_abs_diff_eq;
use fj_math::{Point, Vector};
use nalgebra::UnitQuaternion; use nalgebra::UnitQuaternion;
use parry3d_f64::math::{Isometry, Translation}; use parry3d_f64::math::{Isometry, Translation};
use crate::math::{Point, Vector};
use super::Line; use super::Line;
#[test] #[test]

View File

@ -1,7 +1,7 @@
mod circle; mod circle;
mod line; mod line;
use crate::math::{Point, Scalar, Transform, Vector}; use fj_math::{Point, Scalar, Transform, Vector};
pub use self::{circle::Circle, line::Line}; pub use self::{circle::Circle, line::Line};

View File

@ -1,6 +1,6 @@
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
use crate::math::{self, Vector}; use fj_math::Vector;
/// A point that can be losslessly converted into its canonical form /// A point that can be losslessly converted into its canonical form
/// ///
@ -13,7 +13,7 @@ pub struct Point<const D: usize> {
/// The native form of the point is its representation in its native /// The native form of the point is its representation in its native
/// coordinate system. This could be a 1-dimensional curve, 2-dimensional /// coordinate system. This could be a 1-dimensional curve, 2-dimensional
/// surface, or 3-dimensional model coordinate system. /// surface, or 3-dimensional model coordinate system.
native: math::Point<D>, native: fj_math::Point<D>,
/// The canonical form of the point /// The canonical form of the point
/// ///
@ -21,7 +21,7 @@ pub struct Point<const D: usize> {
/// kept here, unchanged, as the point is converted into other coordinate /// kept here, unchanged, as the point is converted into other coordinate
/// systems, it allows for a lossless conversion back into 3D coordinates, /// systems, it allows for a lossless conversion back into 3D coordinates,
/// unaffected by floating point accuracy issues. /// unaffected by floating point accuracy issues.
canonical: math::Point<3>, canonical: fj_math::Point<3>,
} }
impl<const D: usize> Point<D> { impl<const D: usize> Point<D> {
@ -29,23 +29,26 @@ impl<const D: usize> Point<D> {
/// ///
/// Both the native and the canonical form must be provide. The caller must /// Both the native and the canonical form must be provide. The caller must
/// guarantee that both of them match. /// guarantee that both of them match.
pub fn new(native: math::Point<D>, canonical: math::Point<3>) -> Self { pub fn new(
native: fj_math::Point<D>,
canonical: fj_math::Point<3>,
) -> Self {
Self { native, canonical } Self { native, canonical }
} }
/// Access the point's native form /// Access the point's native form
pub fn native(&self) -> math::Point<D> { pub fn native(&self) -> fj_math::Point<D> {
self.native self.native
} }
/// Access the point's canonical form /// Access the point's canonical form
pub fn canonical(&self) -> math::Point<3> { pub fn canonical(&self) -> fj_math::Point<3> {
self.canonical self.canonical
} }
} }
impl From<math::Point<3>> for Point<3> { impl From<fj_math::Point<3>> for Point<3> {
fn from(point: math::Point<3>) -> Self { fn from(point: fj_math::Point<3>) -> Self {
Self::new(point, point) Self::new(point, point)
} }
} }
@ -54,7 +57,7 @@ impl From<math::Point<3>> for Point<3> {
// `Point`, or the conversion back to 3D would be broken. // `Point`, or the conversion back to 3D would be broken.
impl<const D: usize> Add<Vector<D>> for Point<D> { impl<const D: usize> Add<Vector<D>> for Point<D> {
type Output = math::Point<D>; type Output = fj_math::Point<D>;
fn add(self, rhs: Vector<D>) -> Self::Output { fn add(self, rhs: Vector<D>) -> Self::Output {
self.native.add(rhs) self.native.add(rhs)
@ -69,10 +72,10 @@ impl<const D: usize> Sub<Self> for Point<D> {
} }
} }
impl<const D: usize> Sub<math::Point<D>> for Point<D> { impl<const D: usize> Sub<fj_math::Point<D>> for Point<D> {
type Output = Vector<D>; type Output = Vector<D>;
fn sub(self, rhs: math::Point<D>) -> Self::Output { fn sub(self, rhs: fj_math::Point<D>) -> Self::Output {
self.native.sub(rhs) self.native.sub(rhs)
} }
} }

View File

@ -2,12 +2,10 @@ pub mod swept;
pub use self::swept::Swept; pub use self::swept::Swept;
use fj_math::{Point, Transform, Vector};
use nalgebra::vector; use nalgebra::vector;
use crate::{ use crate::kernel::geometry;
kernel::geometry,
math::{Point, Transform, Vector},
};
use super::{Curve, Line}; use super::{Curve, Line};

View File

@ -1,7 +1,6 @@
use crate::{ use fj_math::{Point, Transform, Vector};
kernel::geometry::Curve,
math::{Point, Transform, Vector}, use crate::kernel::geometry::Curve;
};
/// A surface that was swept from a curve /// A surface that was swept from a curve
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
@ -56,10 +55,9 @@ impl Swept {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use fj_math::{Point, Vector};
kernel::geometry::{Curve, Line},
math::{Point, Vector}, use crate::kernel::geometry::{Curve, Line};
};
use super::Swept; use super::Swept;

View File

@ -1,9 +1,8 @@
use crate::{ use fj_math::{Point, Transform};
kernel::{
use crate::kernel::{
geometry::{Curve, Surface}, geometry::{Curve, Surface},
topology::faces::Face, topology::faces::Face,
},
math::{Point, Transform},
}; };
use super::{ use super::{

View File

@ -4,6 +4,8 @@ pub mod iter;
pub mod topology; pub mod topology;
pub mod validate; pub mod validate;
use fj_math::{Point, Scalar};
pub use self::{ pub use self::{
geometry::Geometry, geometry::Geometry,
handle::Handle, handle::Handle,
@ -12,8 +14,6 @@ pub use self::{
validate::{ValidationError, ValidationResult}, validate::{ValidationError, ValidationResult},
}; };
use crate::math::{Point, Scalar};
use super::{ use super::{
geometry::{Curve, Surface}, geometry::{Curve, Surface},
topology::{ topology::{

View File

@ -1,5 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use fj_math::{Point, Scalar, Triangle, Vector};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
kernel::{ kernel::{
@ -10,7 +12,6 @@ use crate::{
vertices::Vertex, vertices::Vertex,
}, },
}, },
math::{Point, Scalar, Triangle, Vector},
}; };
use super::{ use super::{
@ -260,8 +261,9 @@ impl Topology<'_> {
mod tests { mod tests {
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use crate::{ use fj_math::{Point, Scalar};
kernel::{
use crate::kernel::{
geometry::{Curve, Line, Surface}, geometry::{Curve, Line, Surface},
shape::{handle::Handle, Shape, ValidationError}, shape::{handle::Handle, Shape, ValidationError},
topology::{ topology::{
@ -269,8 +271,6 @@ mod tests {
faces::Face, faces::Face,
vertices::Vertex, vertices::Vertex,
}, },
},
math::{Point, Scalar},
}; };
const MIN_DISTANCE: f64 = 5e-7; const MIN_DISTANCE: f64 = 5e-7;

View File

@ -1,3 +1,5 @@
use fj_math::{Aabb, Point, Scalar};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
kernel::{ kernel::{
@ -5,7 +7,6 @@ use crate::{
shape::Shape, shape::Shape,
topology::{edges::Cycle, faces::Face}, topology::{edges::Cycle, faces::Face},
}, },
math::{Aabb, Point, Scalar},
}; };
use super::ToShape; use super::ToShape;

View File

@ -1,5 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use fj_math::{Aabb, Scalar};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
kernel::{ kernel::{
@ -10,7 +12,6 @@ use crate::{
vertices::Vertex, vertices::Vertex,
}, },
}, },
math::{Aabb, Scalar},
}; };
use super::ToShape; use super::ToShape;

View File

@ -1,5 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use fj_math::{Aabb, Scalar};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
kernel::{ kernel::{
@ -10,7 +12,6 @@ use crate::{
vertices::Vertex, vertices::Vertex,
}, },
}, },
math::{Aabb, Scalar},
}; };
use super::ToShape; use super::ToShape;

View File

@ -5,10 +5,9 @@ pub mod sketch;
pub mod sweep; pub mod sweep;
pub mod transform; pub mod transform;
use crate::{ use fj_math::{Aabb, Scalar};
debug::DebugInfo,
math::{Aabb, Scalar}, use crate::debug::DebugInfo;
};
use super::shape::Shape; use super::shape::Shape;

View File

@ -1,3 +1,5 @@
use fj_math::{Aabb, Point, Scalar};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
kernel::{ kernel::{
@ -5,7 +7,6 @@ use crate::{
shape::Shape, shape::Shape,
topology::{edges::Cycle, faces::Face, vertices::Vertex}, topology::{edges::Cycle, faces::Face, vertices::Vertex},
}, },
math::{Aabb, Point, Scalar},
}; };
use super::ToShape; use super::ToShape;

View File

@ -1,7 +1,8 @@
use fj_math::{Aabb, Scalar, Vector};
use crate::{ use crate::{
debug::DebugInfo, debug::DebugInfo,
kernel::{algorithms::sweep::sweep_shape, shape::Shape}, kernel::{algorithms::sweep::sweep_shape, shape::Shape},
math::{Aabb, Scalar, Vector},
}; };
use super::ToShape; use super::ToShape;

View File

@ -1,10 +1,7 @@
use fj_math::{Aabb, Scalar, Transform};
use parry3d_f64::math::Isometry; use parry3d_f64::math::Isometry;
use crate::{ use crate::{debug::DebugInfo, kernel::shape::Shape};
debug::DebugInfo,
kernel::shape::Shape,
math::{Aabb, Scalar, Transform},
};
use super::ToShape; use super::ToShape;

View File

@ -3,6 +3,7 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
}; };
use fj_math::{Aabb, Scalar, Segment, Triangle};
use parry2d_f64::query::{Ray as Ray2, RayCast as _}; use parry2d_f64::query::{Ray as Ray2, RayCast as _};
use parry3d_f64::query::Ray as Ray3; use parry3d_f64::query::Ray as Ray3;
@ -15,7 +16,6 @@ use crate::{
geometry::Surface, geometry::Surface,
shape::Handle, shape::Handle,
}, },
math::{Aabb, Scalar, Segment, Triangle},
}; };
use super::edges::Cycle; use super::edges::Cycle;

View File

@ -1,6 +1,8 @@
use std::hash::Hash; use std::hash::Hash;
use crate::{kernel::shape::Handle, math::Point}; use fj_math::Point;
use crate::kernel::shape::Handle;
/// A vertex /// A vertex
/// ///

View File

@ -5,7 +5,6 @@ mod debug;
mod graphics; mod graphics;
mod input; mod input;
mod kernel; mod kernel;
mod math;
mod mesh; mod mesh;
mod model; mod model;
mod window; mod window;
@ -15,6 +14,7 @@ use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
use std::{collections::HashMap, sync::mpsc, time::Instant}; use std::{collections::HashMap, sync::mpsc, time::Instant};
use fj_math::Scalar;
use futures::executor::block_on; use futures::executor::block_on;
use notify::Watcher as _; use notify::Watcher as _;
use tracing::trace; use tracing::trace;
@ -25,7 +25,6 @@ use winit::{
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
}; };
use crate::math::Scalar;
use crate::{ use crate::{
args::Args, args::Args,
camera::Camera, camera::Camera,
@ -105,7 +104,7 @@ fn main() -> anyhow::Result<()> {
// look at the smallest non-zero extent of the bounding box and divide that // look at the smallest non-zero extent of the bounding box and divide that
// by some value. // by some value.
let mut min_extent = Scalar::MAX; let mut min_extent = Scalar::MAX;
for extent in aabb.size().components() { for extent in aabb.size().components {
if extent > Scalar::ZERO && extent < min_extent { if extent > Scalar::ZERO && extent < min_extent {
min_extent = extent; min_extent = extent;
} }

View File

@ -1,22 +0,0 @@
use super::Scalar;
/// 1-dimensional curve coordinates
#[repr(C)]
pub struct T {
pub t: Scalar,
}
/// 2-dimensional surface coordinates
#[repr(C)]
pub struct Uv {
pub u: Scalar,
pub v: Scalar,
}
/// 3-dimensional model coordinates
#[repr(C)]
pub struct Xyz {
pub x: Scalar,
pub y: Scalar,
pub z: Scalar,
}

View File

@ -1,13 +0,0 @@
pub mod aabb;
pub mod coordinates;
pub mod point;
pub mod scalar;
pub mod segment;
pub mod transform;
pub mod triangle;
pub mod vector;
pub use self::{
aabb::Aabb, point::Point, scalar::Scalar, segment::Segment,
transform::Transform, triangle::Triangle, vector::Vector,
};

19
fj-math/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "fj-math"
version = "0.5.0"
edition = "2021"
description = "The world needs another CAD program."
readme = "../README.md"
repository = "https://github.com/hannobraun/fornjot"
license = "0BSD"
keywords = ["cad", "programmatic", "code-cad"]
categories = ["mathematics"]
[dependencies]
approx = "0.5.1"
decorum = "0.3.1"
nalgebra = "0.30.0"
num-traits = "0.2.14"
parry2d-f64 = "0.8.0"
parry3d-f64 = "0.8.0"

View File

@ -0,0 +1,31 @@
use super::Scalar;
/// 1-dimensional curve coordinates
#[repr(C)]
pub struct T {
/// The single coordinate of the 1-dimensional curve coordinates
pub t: Scalar,
}
/// 2-dimensional surface coordinates
#[repr(C)]
pub struct Uv {
/// The first coordinate of the 2-dimensional surface coordinates
pub u: Scalar,
/// The second coordinate of the 2-dimensional surface coordinates
pub v: Scalar,
}
/// 3-dimensional model coordinates
#[repr(C)]
pub struct Xyz {
/// The first coordinate of the 3-dimensional model coordinates
pub x: Scalar,
/// The second coordinate of the 3-dimensional model coordinates
pub y: Scalar,
/// The third coordinate of the 3-dimensional model coordinates
pub z: Scalar,
}

48
fj-math/src/lib.rs Normal file
View File

@ -0,0 +1,48 @@
//! Math primitives for the Fornjot ecosystem
//!
//! This crates provides basic math types for the Fornjot ecosystem. It is built
//! on [nalgebra] and [Parry], but provides an interface that is specifically
//! tailored to the needs of Fornjot.
//!
//! # Failing [`From`]/[`Into`] implementations
//!
//! Please note that any [`From`]/[`Into`] implementation that convert floating
//! point numbers into [`Scalar`] can panic. These conversions call
//! [`Scalar::from_f64`] internally and panic under the same conditions. This
//! affects [`Scalar`] itself, but also any other types in this crate that
//! provide conversions from types that involve `f64`.
//!
//! This explicitly goes against the mandate of [`From`]/[`Into`], whose
//! documentation states that implementations must not fail. This is a
//! deliberate design decision. The intended use case of `Scalar` is math code
//! that considers NaN results a bug, not a recoverable error.
//!
//! For this use case, having easy conversions available is an advantage, and
//! explicit `unwrap`/`expect` calls would add nothing. In addition, the
//! [`From`]/[`Into`] documentation fails to provide any reasons for its
//! mandate.
//!
//! [nalgebra]: https://nalgebra.org/
//! [Parry]: https://www.parry.rs/
#![deny(missing_docs)]
mod aabb;
mod coordinates;
mod point;
mod scalar;
mod segment;
mod transform;
mod triangle;
mod vector;
pub use self::{
aabb::Aabb,
coordinates::{Uv, Xyz, T},
point::Point,
scalar::Scalar,
segment::Segment,
transform::Transform,
triangle::Triangle,
vector::Vector,
};

View File

@ -7,14 +7,11 @@ use super::{
/// An n-dimensional point /// An n-dimensional point
/// ///
/// The dimensionality is defined by the const generic argument `D`. /// The dimensionality of the point is defined by the const generic `D`
/// /// parameter.
/// # Implementation Note
///
/// The goal of this type is to eventually implement `Eq` and `Hash`, making it
/// easier to work with vectors. This is a work in progress.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Point<const D: usize> { pub struct Point<const D: usize> {
/// The coordinates of the point
pub coords: Vector<D>, pub coords: Vector<D>,
} }

View File

@ -8,23 +8,6 @@ use decorum::R64;
/// value is not NaN. This allows `Scalar` to provide implementations of [`Eq`], /// value is not NaN. This allows `Scalar` to provide implementations of [`Eq`],
/// [`Ord`], and [`Hash`], enabling `Scalar` (and types built on top of it), to /// [`Ord`], and [`Hash`], enabling `Scalar` (and types built on top of it), to
/// be used as keys in hash maps, hash sets, and similar types. /// be used as keys in hash maps, hash sets, and similar types.
///
/// # Failing `From`/`Into` implementations
///
/// Please note that the [`From`]/[`Into`] implementation that convert floating
/// point numbers into `Scalar` can panic. These conversions call
/// [`Scalar::from_f64`] internally and panic under the same conditions.
///
/// This explicitly goes against the mandate of [`From`]/[`Into`], whose
/// documentation mandate that implementations must not fail. This is a
/// deliberate design decision. The intended use case of `Scalar` is math code
/// that considers non-finite floating point values a bug, not a recoverable
/// error.
///
/// For this use case, having easy conversions available is an advantage, and
/// explicit `unwrap`/`expect` calls would add nothing. In addition, the mandate
/// not to fail is not motivated in any way, in the [`From`]/[`Into`]
/// documentation.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Scalar(f64); pub struct Scalar(f64);
@ -46,6 +29,8 @@ impl Scalar {
/// Construct a `Scalar` from an `f64` /// Construct a `Scalar` from an `f64`
/// ///
/// # Panics
///
/// Panics, if `scalar` is NaN. /// Panics, if `scalar` is NaN.
pub fn from_f64(scalar: f64) -> Self { pub fn from_f64(scalar: f64) -> Self {
if scalar.is_nan() { if scalar.is_nan() {

View File

@ -3,48 +3,56 @@ use std::fmt;
use super::Point; use super::Point;
/// A line segment, defined by its two end points /// A line segment, defined by its two end points
///
/// The dimensionality of the segment is defined by the const generic `D`
/// parameter.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Segment<const D: usize> { pub struct Segment<const D: usize> {
a: Point<D>, points: [Point<D>; 2],
b: Point<D>,
} }
impl<const D: usize> Segment<D> { impl<const D: usize> Segment<D> {
/// Construct a segment from two points
///
/// # Panics
///
/// Panics, if the points are coincident.
pub fn from_points(points: [Point<D>; 2]) -> Self {
let [a, b] = points;
assert!(a != b, "Invalid segment; both points are identical {a:?}");
Self { points }
}
/// Access the points of the segment /// Access the points of the segment
pub fn points(&self) -> [Point<D>; 2] { pub fn points(&self) -> [Point<D>; 2] {
[self.a, self.b] self.points
} }
} }
impl Segment<2> { impl Segment<2> {
/// Convert the 2-dimensional segment to a Parry segment /// Convert the 2-dimensional segment to a Parry segment
pub fn to_parry(self) -> parry2d_f64::shape::Segment { pub fn to_parry(self) -> parry2d_f64::shape::Segment {
[self.a.to_na(), self.b.to_na()].into() self.points.map(|point| point.to_na()).into()
} }
} }
impl Segment<3> { impl Segment<3> {
/// Convert the 3-dimensional segment to a Parry segment /// Convert the 3-dimensional segment to a Parry segment
pub fn to_parry(self) -> parry3d_f64::shape::Segment { pub fn to_parry(self) -> parry3d_f64::shape::Segment {
[self.a.to_na(), self.b.to_na()].into() self.points.map(|point| point.to_na()).into()
} }
} }
impl<const D: usize> From<[Point<D>; 2]> for Segment<D> { impl<const D: usize> From<[Point<D>; 2]> for Segment<D> {
fn from(points: [Point<D>; 2]) -> Self { fn from(points: [Point<D>; 2]) -> Self {
let [a, b] = points; Self::from_points(points)
assert!(a != b, "Invalid segment; both points are identical {a:?}");
Self {
a: points[0],
b: points[1],
}
} }
} }
impl<const D: usize> fmt::Debug for Segment<D> { impl<const D: usize> fmt::Debug for Segment<D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{:?} -> {:?}]", self.a, self.b) write!(f, "[{:?} -> {:?}]", self.points[0], self.points[1])
} }
} }

View File

@ -1,6 +1,9 @@
use super::{Point, Scalar}; use super::{Point, Scalar};
/// A triangle /// A triangle
///
/// The dimensionality of the triangle is defined by the const generic `D`
/// parameter.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Triangle<const D: usize> { pub struct Triangle<const D: usize> {
points: [Point<D>; 3], points: [Point<D>; 3],
@ -8,6 +11,28 @@ pub struct Triangle<const D: usize> {
} }
impl<const D: usize> Triangle<D> { impl<const D: usize> Triangle<D> {
/// Construct a triangle from three points
///
/// # Panics
///
/// Panics, if the points don't form a triangle.
pub fn from_points(points: [Point<D>; 3]) -> Self {
let area = {
let [a, b, c] = points.map(Point::to_xyz);
(b - a).cross(&(c - a)).magnitude()
};
// A triangle is not valid if it doesn't span any area
if area != Scalar::from(0.0) {
Self {
points,
color: [255, 0, 0, 255],
}
} else {
panic!("Invalid Triangle specified");
}
}
/// Access the triangle's points /// Access the triangle's points
pub fn points(&self) -> [Point<D>; 3] { pub fn points(&self) -> [Point<D>; 3] {
self.points self.points
@ -33,27 +58,15 @@ impl Triangle<3> {
impl<const D: usize> From<[Point<D>; 3]> for Triangle<D> { impl<const D: usize> From<[Point<D>; 3]> for Triangle<D> {
fn from(points: [Point<D>; 3]) -> Self { fn from(points: [Point<D>; 3]) -> Self {
let area = { Self::from_points(points)
let [a, b, c] = points.map(Point::to_xyz);
(b - a).cross(&(c - a)).magnitude()
};
// A triangle is not valid if it doesn't span any area
if area != Scalar::from(0.0) {
Self {
points,
color: [255, 0, 0, 255],
}
} else {
panic!("Invalid Triangle specified");
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::Point;
use super::Triangle; use super::Triangle;
use crate::math::Point;
#[test] #[test]
fn valid_triangle_2d() { fn valid_triangle_2d() {

View File

@ -7,34 +7,42 @@ use super::{
/// An n-dimensional vector /// An n-dimensional vector
/// ///
/// The dimensionality is defined by the const generic argument `D`. /// The dimensionality of the vector is defined by the const generic `D`
/// /// parameter.
/// # Implementation Note
///
/// The goal of this type is to eventually implement `Eq` and `Hash`, making it
/// easier to work with vectors. This is a work in progress.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Vector<const D: usize>(pub [Scalar; D]); pub struct Vector<const D: usize> {
/// The vector components
pub components: [Scalar; D],
}
impl<const D: usize> Vector<D> { impl<const D: usize> Vector<D> {
/// Construct a `Vector` from an array /// Construct a `Vector` from `f64` components
pub fn from_array(array: [f64; D]) -> Self { ///
Self(array.map(Scalar::from_f64)) /// # Panics
///
/// Panics, if the components can not be converted to [`Scalar`]. See
/// [`Scalar::from_f64`], which this method uses internally.
pub fn from_components_f64(components: [f64; D]) -> Self {
Self {
components: components.map(Scalar::from_f64),
}
} }
/// Construct a `Vector` from an nalgebra vector /// Construct a `Vector` from an nalgebra vector
pub fn from_na(vector: nalgebra::SVector<f64, D>) -> Self { pub fn from_na(vector: nalgebra::SVector<f64, D>) -> Self {
Self::from_array(vector.into()) Self::from_components_f64(vector.into())
} }
/// Convert the vector into an nalgebra vector /// Convert the vector into an nalgebra vector
pub fn to_na(self) -> nalgebra::SVector<f64, D> { pub fn to_na(self) -> nalgebra::SVector<f64, D> {
self.0.map(Scalar::into_f64).into() self.components.map(Scalar::into_f64).into()
} }
/// Convert to a 1-dimensional vector /// Convert to a 1-dimensional vector
pub fn to_t(self) -> Vector<1> { pub fn to_t(self) -> Vector<1> {
Vector([self.0[0]]) Vector {
components: [self.components[0]],
}
} }
/// Convert the vector into a 3-dimensional vector /// Convert the vector into a 3-dimensional vector
@ -47,14 +55,14 @@ impl<const D: usize> Vector<D> {
pub fn to_xyz(self) -> Vector<3> { pub fn to_xyz(self) -> Vector<3> {
let zero = Scalar::ZERO; let zero = Scalar::ZERO;
let components = match self.0.as_slice() { let components = match self.components.as_slice() {
[] => [zero, zero, zero], [] => [zero, zero, zero],
&[t] => [t, zero, zero], &[t] => [t, zero, zero],
&[u, v] => [u, v, zero], &[u, v] => [u, v, zero],
&[x, y, z, ..] => [x, y, z], &[x, y, z, ..] => [x, y, z],
}; };
Vector(components) Vector { components }
} }
/// Compute the magnitude of the vector /// Compute the magnitude of the vector
@ -71,11 +79,6 @@ impl<const D: usize> Vector<D> {
pub fn dot(&self, other: &Self) -> Scalar { pub fn dot(&self, other: &Self) -> Scalar {
self.to_na().dot(&other.to_na()).into() self.to_na().dot(&other.to_na()).into()
} }
/// Access an iterator over the vector's components
pub fn components(&self) -> [Scalar; D] {
self.0
}
} }
impl Vector<3> { impl Vector<3> {
@ -94,7 +97,7 @@ impl ops::Deref for Vector<1> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
let ptr = self.0.as_ptr() as *const Self::Target; let ptr = self.components.as_ptr() as *const Self::Target;
// This is sound. We've created this pointer from a valid instance, that // This is sound. We've created this pointer from a valid instance, that
// has the same size and layout as the target. // has the same size and layout as the target.
@ -106,7 +109,7 @@ impl ops::Deref for Vector<2> {
type Target = Uv; type Target = Uv;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
let ptr = self.0.as_ptr() as *const Self::Target; let ptr = self.components.as_ptr() as *const Self::Target;
// This is sound. We've created this pointer from a valid instance, that // This is sound. We've created this pointer from a valid instance, that
// has the same size and layout as the target. // has the same size and layout as the target.
@ -118,7 +121,7 @@ impl ops::Deref for Vector<3> {
type Target = Xyz; type Target = Xyz;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
let ptr = self.0.as_ptr() as *const Self::Target; let ptr = self.components.as_ptr() as *const Self::Target;
// This is sound. We've created this pointer from a valid instance, that // This is sound. We've created this pointer from a valid instance, that
// has the same size and layout as the target. // has the same size and layout as the target.
@ -128,7 +131,7 @@ impl ops::Deref for Vector<3> {
impl ops::DerefMut for Vector<1> { impl ops::DerefMut for Vector<1> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
let ptr = self.0.as_mut_ptr() as *mut Self::Target; let ptr = self.components.as_mut_ptr() as *mut Self::Target;
// This is sound. We've created this pointer from a valid instance, that // This is sound. We've created this pointer from a valid instance, that
// has the same size and layout as the target. // has the same size and layout as the target.
@ -138,7 +141,7 @@ impl ops::DerefMut for Vector<1> {
impl ops::DerefMut for Vector<2> { impl ops::DerefMut for Vector<2> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
let ptr = self.0.as_mut_ptr() as *mut Self::Target; let ptr = self.components.as_mut_ptr() as *mut Self::Target;
// This is sound. We've created this pointer from a valid instance, that // This is sound. We've created this pointer from a valid instance, that
// has the same size and layout as the target. // has the same size and layout as the target.
@ -148,7 +151,7 @@ impl ops::DerefMut for Vector<2> {
impl ops::DerefMut for Vector<3> { impl ops::DerefMut for Vector<3> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
let ptr = self.0.as_mut_ptr() as *mut Self::Target; let ptr = self.components.as_mut_ptr() as *mut Self::Target;
// This is sound. We've created this pointer from a valid instance, that // This is sound. We've created this pointer from a valid instance, that
// has the same size and layout as the target. // has the same size and layout as the target.
@ -157,14 +160,14 @@ impl ops::DerefMut for Vector<3> {
} }
impl<const D: usize> From<[Scalar; D]> for Vector<D> { impl<const D: usize> From<[Scalar; D]> for Vector<D> {
fn from(array: [Scalar; D]) -> Self { fn from(components: [Scalar; D]) -> Self {
Self(array) Self { components }
} }
} }
impl<const D: usize> From<[f64; D]> for Vector<D> { impl<const D: usize> From<[f64; D]> for Vector<D> {
fn from(array: [f64; D]) -> Self { fn from(components: [f64; D]) -> Self {
Self::from_array(array) Self::from_components_f64(components)
} }
} }
@ -176,19 +179,19 @@ impl<const D: usize> From<nalgebra::SVector<f64, D>> for Vector<D> {
impl<const D: usize> From<Vector<D>> for [f32; D] { impl<const D: usize> From<Vector<D>> for [f32; D] {
fn from(vector: Vector<D>) -> Self { fn from(vector: Vector<D>) -> Self {
vector.0.map(|scalar| scalar.into_f32()) vector.components.map(|scalar| scalar.into_f32())
} }
} }
impl<const D: usize> From<Vector<D>> for [f64; D] { impl<const D: usize> From<Vector<D>> for [f64; D] {
fn from(vector: Vector<D>) -> Self { fn from(vector: Vector<D>) -> Self {
vector.0.map(|scalar| scalar.into_f64()) vector.components.map(|scalar| scalar.into_f64())
} }
} }
impl<const D: usize> From<Vector<D>> for [Scalar; D] { impl<const D: usize> From<Vector<D>> for [Scalar; D] {
fn from(vector: Vector<D>) -> Self { fn from(vector: Vector<D>) -> Self {
vector.0 vector.components
} }
} }
@ -224,7 +227,7 @@ impl<const D: usize> ops::Div<Scalar> for Vector<D> {
impl<const D: usize> fmt::Debug for Vector<D> { impl<const D: usize> fmt::Debug for Vector<D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f) self.components.fmt(f)
} }
} }
@ -236,13 +239,13 @@ impl<const D: usize> approx::AbsDiffEq for Vector<D> {
} }
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.0.abs_diff_eq(&other.0, epsilon) self.components.abs_diff_eq(&other.components, epsilon)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::math::Vector; use crate::Vector;
#[test] #[test]
fn to_xyz() { fn to_xyz() {