Another project
0

Configure Feed

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

1use bone_types::dimensioned_serde; 2use bone_types::{ 3 Aabb3, Angle, ChordHeightTolerance, Length, Parameter, Plane3, Point3, Tolerance, UnitVec3, 4 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::circular3; 13use crate::closest::ClosestPoint3; 14use crate::curve3::{Curve3, Curve3Kind}; 15 16#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 17#[serde(deny_unknown_fields)] 18pub struct Circle3 { 19 plane: Plane3, 20 #[serde(with = "dimensioned_serde::length_si")] 21 radius: Length, 22} 23 24impl Circle3 { 25 pub fn new(plane: Plane3, radius: Length, tolerance: Tolerance) -> Result<Self, KernelError> { 26 if radius.get::<millimeter>() < tolerance.value() { 27 return Err(KernelError::DegenerateCircle); 28 } 29 Ok(Self { plane, radius }) 30 } 31 32 #[must_use] 33 pub(crate) const fn from_raw(plane: Plane3, radius: Length) -> Self { 34 Self { plane, radius } 35 } 36 37 #[must_use] 38 pub fn center(self) -> Point3 { 39 self.plane.origin() 40 } 41 42 #[must_use] 43 pub const fn plane(self) -> Plane3 { 44 self.plane 45 } 46 47 #[must_use] 48 pub fn normal(self) -> UnitVec3 { 49 self.plane.normal() 50 } 51 52 #[must_use] 53 pub const fn radius(self) -> Length { 54 self.radius 55 } 56 57 #[must_use] 58 pub fn radius_mm(self) -> f64 { 59 self.radius.get::<millimeter>() 60 } 61 62 #[must_use] 63 pub fn as_kind(self) -> Curve3Kind { 64 Curve3Kind::Circle(self) 65 } 66} 67 68impl Curve3 for Circle3 { 69 fn evaluate(&self, t: Parameter) -> Point3 { 70 circular3::point_at( 71 self.plane, 72 self.radius, 73 Angle::new::<radian>(TAU * t.value()), 74 ) 75 } 76 77 fn derivative(&self, t: Parameter) -> Vec3 { 78 circular3::tangent_vec( 79 self.plane, 80 self.radius, 81 Angle::new::<radian>(TAU * t.value()), 82 ) * TAU 83 } 84 85 fn bounding_box(&self) -> Aabb3 { 86 circular3::bounding_box( 87 self.plane, 88 self.radius, 89 Angle::new::<radian>(0.0), 90 Angle::new::<radian>(TAU), 91 ) 92 } 93 94 fn closest_point(&self, p: Point3, tolerance: Tolerance) -> ClosestPoint3 { 95 let (px, py, pn) = circular3::local_coords(self.plane, p); 96 let radial = (px * px + py * py).sqrt(); 97 let r = self.radius_mm(); 98 let theta = if radial < tolerance.value() { 99 0.0 100 } else { 101 py.atan2(px) 102 }; 103 let point = circular3::point_at(self.plane, self.radius, Angle::new::<radian>(theta)); 104 let t = (theta / TAU).rem_euclid(1.0); 105 let distance = Length::new::<millimeter>(((radial - r).powi(2) + pn * pn).sqrt()); 106 ClosestPoint3::new(Parameter::new(t), point, distance) 107 } 108 109 fn tessellate(&self, tolerance: ChordHeightTolerance) -> Vec<Point3> { 110 let n = circular3::sample_count(self.radius, Angle::new::<radian>(TAU), tolerance, 3); 111 (0..n) 112 .map(|i| { 113 circular3::point_at( 114 self.plane, 115 self.radius, 116 Angle::new::<radian>(TAU * f64::from(i) / f64::from(n)), 117 ) 118 }) 119 .collect() 120 } 121} 122 123impl core::fmt::Display for Circle3 { 124 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 125 write!( 126 f, 127 "circle3{{ c={}, r={} mm, normal={} }}", 128 self.center(), 129 self.radius_mm(), 130 self.normal(), 131 ) 132 } 133}