Another project
0

Configure Feed

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

at main 5.5 kB View raw
1use bone_types::dimensioned_serde; 2use bone_types::{ 3 Aabb3, Angle, AngleTolerance, ChordHeightTolerance, Length, Parameter, Plane3, Point3, 4 Tolerance, UnitVec3, Vec3, 5}; 6use core::f64::consts::TAU; 7use serde::{Deserialize, Serialize}; 8use uom::si::angle::radian; 9use uom::si::length::millimeter; 10 11use crate::KernelError; 12use crate::angles; 13use crate::circle3::Circle3; 14use crate::circular3; 15use crate::closest::ClosestPoint3; 16use crate::curve3::{Curve3, Curve3Kind}; 17 18#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 19#[serde(deny_unknown_fields)] 20pub struct Arc3 { 21 plane: Plane3, 22 #[serde(with = "dimensioned_serde::length_si")] 23 radius: Length, 24 #[serde(with = "dimensioned_serde::angle_si")] 25 start_angle: Angle, 26 #[serde(with = "dimensioned_serde::angle_si")] 27 sweep_angle: Angle, 28} 29 30impl Arc3 { 31 pub fn new( 32 plane: Plane3, 33 radius: Length, 34 start_angle: Angle, 35 sweep_angle: Angle, 36 tolerance: Tolerance, 37 ) -> Result<Self, KernelError> { 38 if radius.get::<millimeter>() < tolerance.value() { 39 return Err(KernelError::DegenerateArc); 40 } 41 let sweep = sweep_angle.get::<radian>(); 42 let angle_eps = AngleTolerance::from_arc_length(tolerance, radius).radians(); 43 if sweep.abs() < angle_eps { 44 return Err(KernelError::DegenerateArc); 45 } 46 if sweep.abs() > TAU + angle_eps { 47 return Err(KernelError::DegenerateArc); 48 } 49 Ok(Self { 50 plane, 51 radius, 52 start_angle, 53 sweep_angle, 54 }) 55 } 56 57 #[must_use] 58 pub fn center(self) -> Point3 { 59 self.plane.origin() 60 } 61 62 #[must_use] 63 pub const fn plane(self) -> Plane3 { 64 self.plane 65 } 66 67 #[must_use] 68 pub fn normal(self) -> UnitVec3 { 69 self.plane.normal() 70 } 71 72 #[must_use] 73 pub const fn radius(self) -> Length { 74 self.radius 75 } 76 77 #[must_use] 78 pub const fn start_angle(self) -> Angle { 79 self.start_angle 80 } 81 82 #[must_use] 83 pub const fn sweep_angle(self) -> Angle { 84 self.sweep_angle 85 } 86 87 #[must_use] 88 pub fn radius_mm(self) -> f64 { 89 self.radius.get::<millimeter>() 90 } 91 92 #[must_use] 93 pub fn start_rad(self) -> f64 { 94 self.start_angle.get::<radian>() 95 } 96 97 #[must_use] 98 pub fn sweep_rad(self) -> f64 { 99 self.sweep_angle.get::<radian>() 100 } 101 102 #[must_use] 103 pub fn as_full_circle(self) -> Circle3 { 104 Circle3::from_raw(self.plane, self.radius) 105 } 106 107 #[must_use] 108 pub fn as_kind(self) -> Curve3Kind { 109 Curve3Kind::Arc(self) 110 } 111 112 #[must_use] 113 pub fn contains_point(self, p: Point3, tolerance: Tolerance) -> bool { 114 let (px, py, pn) = circular3::local_coords(self.plane, p); 115 let radial = (px * px + py * py).sqrt(); 116 if (radial - self.radius_mm()).abs() > tolerance.value() || pn.abs() > tolerance.value() { 117 return false; 118 } 119 let angle_eps = AngleTolerance::from_arc_length(tolerance, self.radius).radians(); 120 angles::contains(py.atan2(px), self.start_rad(), self.sweep_rad(), angle_eps) 121 } 122 123 fn angle_at(self, t: Parameter) -> Angle { 124 Angle::new::<radian>(self.start_rad() + self.sweep_rad() * t.value()) 125 } 126} 127 128impl Curve3 for Arc3 { 129 fn evaluate(&self, t: Parameter) -> Point3 { 130 circular3::point_at(self.plane, self.radius, self.angle_at(t)) 131 } 132 133 fn derivative(&self, t: Parameter) -> Vec3 { 134 circular3::tangent_vec(self.plane, self.radius, self.angle_at(t)) * self.sweep_rad() 135 } 136 137 fn bounding_box(&self) -> Aabb3 { 138 circular3::bounding_box(self.plane, self.radius, self.start_angle, self.sweep_angle) 139 } 140 141 fn closest_point(&self, p: Point3, tolerance: Tolerance) -> ClosestPoint3 { 142 let (px, py, _) = circular3::local_coords(self.plane, p); 143 let radial = (px * px + py * py).sqrt(); 144 let theta_unclamped = if radial < tolerance.value() { 145 self.start_rad() 146 } else { 147 py.atan2(px) 148 }; 149 let theta = angles::clamp(theta_unclamped, self.start_rad(), self.sweep_rad(), 0.0); 150 let sweep = self.sweep_rad(); 151 let delta = if sweep >= 0.0 { 152 (theta - self.start_rad()).rem_euclid(TAU) 153 } else { 154 (self.start_rad() - theta).rem_euclid(TAU) 155 }; 156 let t = (delta / sweep.abs()).clamp(0.0, 1.0); 157 let point = circular3::point_at(self.plane, self.radius, Angle::new::<radian>(theta)); 158 ClosestPoint3::new(Parameter::new(t), point, (p - point).norm()) 159 } 160 161 fn tessellate(&self, tolerance: ChordHeightTolerance) -> Vec<Point3> { 162 let (start, sweep) = (self.start_rad(), self.sweep_rad()); 163 let n = circular3::sample_count(self.radius, self.sweep_angle, tolerance, 1); 164 (0..=n) 165 .map(|i| { 166 circular3::point_at( 167 self.plane, 168 self.radius, 169 Angle::new::<radian>(start + sweep * f64::from(i) / f64::from(n)), 170 ) 171 }) 172 .collect() 173 } 174} 175 176impl core::fmt::Display for Arc3 { 177 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 178 write!( 179 f, 180 "arc3{{ c={}, r={} mm, start={} rad, sweep={} rad, normal={} }}", 181 self.center(), 182 self.radius_mm(), 183 self.start_rad(), 184 self.sweep_rad(), 185 self.normal(), 186 ) 187 } 188}