Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor(kernel): angle helpers, closest/intersection point-generic

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (May 25, 2026, 9:53 AM +0300) commit b9914927 parent e2242010 change-id unttnstu
+86 -63
+27
crates/bone-kernel/src/angles.rs
··· 1 + use core::f64::consts::{PI, TAU}; 2 + 3 + #[must_use] 4 + pub(crate) fn wrap_delta(a: f64) -> f64 { 5 + (a + PI).rem_euclid(TAU) - PI 6 + } 7 + 8 + #[must_use] 9 + pub(crate) fn contains(theta: f64, start: f64, sweep: f64, eps: f64) -> bool { 10 + let delta = if sweep >= 0.0 { 11 + (theta - start).rem_euclid(TAU) 12 + } else { 13 + (start - theta).rem_euclid(TAU) 14 + }; 15 + delta <= sweep.abs() + eps || delta >= TAU - eps 16 + } 17 + 18 + #[must_use] 19 + pub(crate) fn clamp(theta: f64, start: f64, sweep: f64, eps: f64) -> f64 { 20 + if contains(theta, start, sweep, eps) { 21 + return theta; 22 + } 23 + let end = start + sweep; 24 + let to_start = wrap_delta(theta - start).abs(); 25 + let to_end = wrap_delta(theta - end).abs(); 26 + if to_start <= to_end { start } else { end } 27 + }
+14 -40
crates/bone-kernel/src/arc2.rs
··· 5 5 6 6 use crate::KernelError; 7 7 use crate::aabb::Aabb2; 8 + use crate::angles; 8 9 use crate::circle2::Circle2; 9 10 use crate::closest::ClosestPoint2; 10 11 use crate::curvature::Curvature; ··· 97 98 return false; 98 99 } 99 100 let angle_tol = AngleTolerance::from_arc_length(tolerance, self.radius); 100 - self.contains_angle_rad(dy.atan2(dx), angle_tol) 101 - } 102 - 103 - fn contains_angle_rad(self, theta: f64, tolerance: AngleTolerance) -> bool { 104 - let start = self.start_rad(); 105 - let sweep = self.sweep_rad(); 106 - let eps = tolerance.radians(); 107 - let delta = if sweep >= 0.0 { 108 - (theta - start).rem_euclid(TAU) 109 - } else { 110 - (start - theta).rem_euclid(TAU) 111 - }; 112 - let magnitude = sweep.abs(); 113 - delta <= magnitude + eps || delta >= TAU - eps 101 + angles::contains( 102 + dy.atan2(dx), 103 + self.start_rad(), 104 + self.sweep_rad(), 105 + angle_tol.radians(), 106 + ) 114 107 } 115 108 116 109 fn angle_at(self, t: Parameter) -> f64 { 117 110 self.start_rad() + self.sweep_rad() * t.value() 118 111 } 119 - 120 - fn clamp_angle_rad(self, theta: f64, tolerance: AngleTolerance) -> f64 { 121 - if self.contains_angle_rad(theta, tolerance) { 122 - return theta; 123 - } 124 - let start = self.start_rad(); 125 - let end = start + self.sweep_rad(); 126 - let to_start = wrap_delta(theta - start).abs(); 127 - let to_end = wrap_delta(theta - end).abs(); 128 - if to_start <= to_end { start } else { end } 129 - } 130 112 } 131 113 132 114 impl Curve2 for Arc2 { ··· 173 155 dy.atan2(dx) 174 156 }; 175 157 176 - let theta = self.clamp_angle_rad(theta_unclamped, angle_tol); 158 + let theta = angles::clamp( 159 + theta_unclamped, 160 + self.start_rad(), 161 + self.sweep_rad(), 162 + angle_tol.radians(), 163 + ); 177 164 let sweep = self.sweep_rad(); 178 165 let delta = if sweep >= 0.0 { 179 166 (theta - self.start_rad()).rem_euclid(TAU) ··· 207 194 } 208 195 } 209 196 210 - pub(crate) fn wrap_delta(a: f64) -> f64 { 211 - (a + PI).rem_euclid(TAU) - PI 212 - } 213 - 214 197 #[must_use] 215 198 pub fn arc_bounding_box( 216 199 center: Point2, ··· 233 216 (-PI * 0.5, Point2::from_mm(cx, cy - r)), 234 217 ]; 235 218 cardinals.into_iter().fold(base, |bbox, (theta, extreme)| { 236 - if arc_contains_angle_rad(theta, start, sweep) { 219 + if angles::contains(theta, start, sweep, 0.0) { 237 220 bbox.extend_point(extreme) 238 221 } else { 239 222 bbox 240 223 } 241 224 }) 242 - } 243 - 244 - fn arc_contains_angle_rad(theta: f64, start: f64, sweep: f64) -> bool { 245 - let delta = if sweep >= 0.0 { 246 - (theta - start).rem_euclid(TAU) 247 - } else { 248 - (start - theta).rem_euclid(TAU) 249 - }; 250 - delta <= sweep.abs() 251 225 } 252 226 253 227 #[cfg(test)]
+10 -7
crates/bone-kernel/src/closest.rs
··· 1 - use bone_types::{Length, Parameter, Point2}; 1 + use bone_types::{Length, Parameter, Point2, Point3}; 2 2 3 3 #[derive(Copy, Clone, Debug, PartialEq)] 4 - pub struct ClosestPoint2 { 4 + pub struct ClosestPoint<P> { 5 5 parameter: Parameter, 6 - point: Point2, 6 + point: P, 7 7 distance: Length, 8 8 } 9 9 10 - impl ClosestPoint2 { 10 + pub type ClosestPoint2 = ClosestPoint<Point2>; 11 + pub type ClosestPoint3 = ClosestPoint<Point3>; 12 + 13 + impl<P: Copy> ClosestPoint<P> { 11 14 #[must_use] 12 - pub const fn new(parameter: Parameter, point: Point2, distance: Length) -> Self { 15 + pub const fn new(parameter: Parameter, point: P, distance: Length) -> Self { 13 16 Self { 14 17 parameter, 15 18 point, ··· 23 26 } 24 27 25 28 #[must_use] 26 - pub const fn point(self) -> Point2 { 29 + pub const fn point(self) -> P { 27 30 self.point 28 31 } 29 32 ··· 33 36 } 34 37 } 35 38 36 - impl core::fmt::Display for ClosestPoint2 { 39 + impl<P: core::fmt::Display> core::fmt::Display for ClosestPoint<P> { 37 40 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 38 41 use uom::si::length::millimeter; 39 42 write!(
+17 -14
crates/bone-kernel/src/intersect.rs
··· 1 1 use bone_types::{AngleTolerance, Point2, Tolerance}; 2 2 use core::f64::consts::TAU; 3 3 4 - use crate::arc2::{Arc2, wrap_delta}; 4 + use crate::angles::wrap_delta; 5 + use crate::arc2::Arc2; 5 6 use crate::circle2::Circle2; 6 7 use crate::curve2::Curve2Kind; 7 8 use crate::line2::Line2; 8 9 9 10 #[derive(Copy, Clone, Debug, PartialEq)] 10 - pub enum IntersectionSet { 11 + pub enum IntersectionSet<P> { 11 12 Empty, 12 - One(Point2), 13 - Two(Point2, Point2), 13 + One(P), 14 + Two(P, P), 14 15 Coincident, 15 16 } 16 17 17 - impl core::fmt::Display for IntersectionSet { 18 + pub type IntersectionSet2 = IntersectionSet<Point2>; 19 + 20 + impl<P: core::fmt::Display> core::fmt::Display for IntersectionSet<P> { 18 21 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 19 22 match self { 20 23 Self::Empty => write!(f, "empty"), ··· 26 29 } 27 30 28 31 #[must_use] 29 - pub fn intersect_curves(a: &Curve2Kind, b: &Curve2Kind, tolerance: Tolerance) -> IntersectionSet { 32 + pub fn intersect_curves(a: &Curve2Kind, b: &Curve2Kind, tolerance: Tolerance) -> IntersectionSet2 { 30 33 match (a, b) { 31 34 (Curve2Kind::Line(l), Curve2Kind::Line(m)) => line_line(l, m, tolerance), 32 35 (Curve2Kind::Line(l), Curve2Kind::Circle(c)) ··· 41 44 } 42 45 } 43 46 44 - fn line_line(a: &Line2, b: &Line2, tolerance: Tolerance) -> IntersectionSet { 47 + fn line_line(a: &Line2, b: &Line2, tolerance: Tolerance) -> IntersectionSet2 { 45 48 let (ax, ay) = a.start().coords_mm(); 46 49 let (ux, uy) = a.unit_direction().components(); 47 50 let la = a.length_mm(); ··· 85 88 IntersectionSet::One(Point2::from_mm(ax + t * ux, ay + t * uy)) 86 89 } 87 90 88 - fn line_circle(line: &Line2, circle: &Circle2, tolerance: Tolerance) -> IntersectionSet { 91 + fn line_circle(line: &Line2, circle: &Circle2, tolerance: Tolerance) -> IntersectionSet2 { 89 92 let (ax, ay) = line.start().coords_mm(); 90 93 let (ux, uy) = line.unit_direction().components(); 91 94 let la = line.length_mm(); ··· 124 127 } 125 128 } 126 129 127 - fn line_arc(line: &Line2, arc: &Arc2, tolerance: Tolerance) -> IntersectionSet { 130 + fn line_arc(line: &Line2, arc: &Arc2, tolerance: Tolerance) -> IntersectionSet2 { 128 131 let circle = arc.as_full_circle(); 129 132 match line_circle(line, &circle, tolerance) { 130 133 IntersectionSet::Empty => IntersectionSet::Empty, ··· 140 143 } 141 144 } 142 145 143 - fn circle_circle(a: &Circle2, b: &Circle2, tolerance: Tolerance) -> IntersectionSet { 146 + fn circle_circle(a: &Circle2, b: &Circle2, tolerance: Tolerance) -> IntersectionSet2 { 144 147 let (ax, ay) = a.center().coords_mm(); 145 148 let (bx, by) = b.center().coords_mm(); 146 149 let ra = a.radius_mm(); ··· 185 188 ) 186 189 } 187 190 188 - fn circle_arc(circle: &Circle2, arc: &Arc2, tolerance: Tolerance) -> IntersectionSet { 191 + fn circle_arc(circle: &Circle2, arc: &Arc2, tolerance: Tolerance) -> IntersectionSet2 { 189 192 let arc_full = arc.as_full_circle(); 190 193 match circle_circle(circle, &arc_full, tolerance) { 191 194 IntersectionSet::Empty => IntersectionSet::Empty, ··· 201 204 } 202 205 } 203 206 204 - fn arc_arc(a: &Arc2, b: &Arc2, tolerance: Tolerance) -> IntersectionSet { 207 + fn arc_arc(a: &Arc2, b: &Arc2, tolerance: Tolerance) -> IntersectionSet2 { 205 208 let a_full = a.as_full_circle(); 206 209 let b_full = b.as_full_circle(); 207 210 match circle_circle(&a_full, &b_full, tolerance) { ··· 227 230 } 228 231 } 229 232 230 - fn same_circle_arc_overlap(a: &Arc2, b: &Arc2, tolerance: Tolerance) -> IntersectionSet { 233 + fn same_circle_arc_overlap(a: &Arc2, b: &Arc2, tolerance: Tolerance) -> IntersectionSet2 { 231 234 let eps_angle = AngleTolerance::from_arc_length(tolerance, a.radius()).radians(); 232 235 let (lo_a, mag_a) = canonical_range(a); 233 236 let (lo_b, mag_b) = canonical_range(b); ··· 289 292 wrap_delta(a - b).abs() < eps 290 293 } 291 294 292 - fn filter_two_by_arc(p: Point2, q: Point2, arc: &Arc2, tolerance: Tolerance) -> IntersectionSet { 295 + fn filter_two_by_arc(p: Point2, q: Point2, arc: &Arc2, tolerance: Tolerance) -> IntersectionSet2 { 293 296 let p_in = arc.contains_point(p, tolerance); 294 297 let q_in = arc.contains_point(q, tolerance); 295 298 match (p_in, q_in) {
+18 -2
crates/bone-kernel/src/lib.rs
··· 1 1 pub mod aabb; 2 + mod angles; 2 3 pub mod arc2; 4 + pub mod arc3; 3 5 pub mod circle2; 6 + pub mod circle3; 7 + mod circular3; 4 8 pub mod closest; 5 9 pub mod curvature; 6 10 pub mod curve2; 11 + pub mod curve3; 7 12 pub mod intersect; 13 + pub mod intersect3; 8 14 pub mod line2; 15 + pub mod line3; 16 + pub mod polyline3; 9 17 10 18 pub use aabb::Aabb2; 11 19 pub use arc2::{Arc2, arc_bounding_box}; 20 + pub use arc3::Arc3; 12 21 pub use circle2::Circle2; 13 - pub use closest::ClosestPoint2; 22 + pub use circle3::Circle3; 23 + pub use closest::{ClosestPoint, ClosestPoint2, ClosestPoint3}; 14 24 pub use curvature::Curvature; 15 25 pub use curve2::{Curve2, Curve2Kind}; 16 - pub use intersect::{IntersectionSet, intersect_curves}; 26 + pub use curve3::{Curve3, Curve3Kind}; 27 + pub use intersect::{IntersectionSet, IntersectionSet2, intersect_curves}; 28 + pub use intersect3::{IntersectionSet3, intersect_curves_3}; 17 29 pub use line2::Line2; 30 + pub use line3::Line3; 31 + pub use polyline3::Polyline3; 18 32 19 33 #[derive(Debug, thiserror::Error)] 20 34 pub enum KernelError { ··· 24 38 DegenerateArc, 25 39 #[error("circle radius is within tolerance of zero")] 26 40 DegenerateCircle, 41 + #[error("polyline needs at least two vertices and no zero-length segment")] 42 + DegeneratePolyline, 27 43 } 28 44 29 45 pub type Result<T, E = KernelError> = core::result::Result<T, E>;