Another project
1use bone_types::{Length, Parameter, Point2, Tolerance, UnitVec2, Vec2};
2use core::f64::consts::TAU;
3use uom::si::length::millimeter;
4
5use crate::KernelError;
6use crate::aabb::Aabb2;
7use crate::closest::ClosestPoint2;
8use crate::curvature::Curvature;
9use crate::curve2::{Curve2, Curve2Kind};
10
11#[derive(Copy, Clone, Debug, PartialEq)]
12pub struct Circle2 {
13 center: Point2,
14 radius: Length,
15}
16
17impl Circle2 {
18 pub fn new(center: Point2, radius: Length, tolerance: Tolerance) -> Result<Self, KernelError> {
19 if radius.get::<millimeter>() < tolerance.value() {
20 return Err(KernelError::DegenerateCircle);
21 }
22 Ok(Self { center, radius })
23 }
24
25 #[must_use]
26 pub(crate) const fn from_raw(center: Point2, radius: Length) -> Self {
27 Self { center, radius }
28 }
29
30 #[must_use]
31 pub const fn center(self) -> Point2 {
32 self.center
33 }
34
35 #[must_use]
36 pub const fn radius(self) -> Length {
37 self.radius
38 }
39
40 #[must_use]
41 pub fn radius_mm(self) -> f64 {
42 self.radius.get::<millimeter>()
43 }
44}
45
46impl Curve2 for Circle2 {
47 fn evaluate(&self, t: Parameter) -> Point2 {
48 let (cx, cy) = self.center.coords_mm();
49 let r = self.radius_mm();
50 let theta = TAU * t.value();
51 Point2::from_mm(cx + r * theta.cos(), cy + r * theta.sin())
52 }
53
54 fn derivative(&self, t: Parameter) -> Vec2 {
55 let r = self.radius_mm();
56 let theta = TAU * t.value();
57 Vec2::from_mm(-r * TAU * theta.sin(), r * TAU * theta.cos())
58 }
59
60 fn tangent(&self, t: Parameter) -> UnitVec2 {
61 let theta = TAU * t.value();
62 UnitVec2::new_unchecked(-theta.sin(), theta.cos())
63 }
64
65 fn curvature(&self, _t: Parameter) -> Curvature {
66 Curvature::from_radius(self.radius)
67 }
68
69 fn bounding_box(&self) -> Aabb2 {
70 let (cx, cy) = self.center.coords_mm();
71 let r = self.radius_mm();
72 Aabb2::from_corners(
73 Point2::from_mm(cx - r, cy - r),
74 Point2::from_mm(cx + r, cy + r),
75 )
76 }
77
78 fn closest_point(&self, p: Point2, tolerance: Tolerance) -> ClosestPoint2 {
79 let (cx, cy) = self.center.coords_mm();
80 let (px, py) = p.coords_mm();
81 let dx = px - cx;
82 let dy = py - cy;
83 let dist = (dx * dx + dy * dy).sqrt();
84 let r = self.radius_mm();
85 if dist < tolerance.value() {
86 let projected = Point2::from_mm(cx + r, cy);
87 let distance = Length::new::<millimeter>(r);
88 return ClosestPoint2::new(Parameter::new(0.0), projected, distance);
89 }
90 let theta = dy.atan2(dx);
91 let t_raw = theta / TAU;
92 let t = t_raw.rem_euclid(1.0);
93 let projected = Point2::from_mm(cx + r * theta.cos(), cy + r * theta.sin());
94 let distance = Length::new::<millimeter>((dist - r).abs());
95 ClosestPoint2::new(Parameter::new(t), projected, distance)
96 }
97
98 fn as_kind(&self) -> Curve2Kind {
99 Curve2Kind::Circle(*self)
100 }
101}
102
103impl core::fmt::Display for Circle2 {
104 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 write!(
106 f,
107 "circle2{{ c={}, r={} mm }}",
108 self.center,
109 self.radius.get::<millimeter>(),
110 )
111 }
112}