Another project
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}