1use num_traits::Float;
2
3use crate::{Point, Scalar, Vector};
4
5pub struct Arc {
7 pub center: Point<2>,
9
10 pub radius: Scalar,
12
13 pub start_angle: Scalar,
15
16 pub end_angle: Scalar,
18}
19
20impl Arc {
21 pub fn from_endpoints_and_angle(
23 p0: impl Into<Point<2>>,
24 p1: impl Into<Point<2>>,
25 angle_rad: Scalar,
26 ) -> Self {
27 let p0 = p0.into();
28 let p1 = p1.into();
29
30 let distance_between_endpoints = (p1 - p0).magnitude();
34 let more_than_half_turn = angle_rad.abs() > Scalar::PI;
35
36 let radius = distance_between_endpoints
37 / (2. * (angle_rad.abs().into_f64() / 2.).sin());
38
39 let center = {
40 let midpoint = Point {
41 coords: (p0.coords + p1.coords) / 2.,
42 };
43 let unit_vector_midpoint_to_center = {
44 let clockwise_turn = angle_rad <= Scalar::ZERO;
45 let f = match (clockwise_turn, more_than_half_turn) {
46 (false, false) | (true, true) => Scalar::ONE,
47 (false, true) | (true, false) => -Scalar::ONE,
48 };
49
50 let unit_vector_p0_to_p1 =
51 (p1 - p0) / distance_between_endpoints * f;
52
53 Vector::from([-unit_vector_p0_to_p1.v, unit_vector_p0_to_p1.u])
54 };
55 let distance_center_to_midpoint = (radius.powi(2)
56 - (distance_between_endpoints.powi(2) / 4.))
57 .sqrt();
58
59 midpoint
60 + unit_vector_midpoint_to_center * distance_center_to_midpoint
61 };
62
63 let start_angle = {
64 let from_center = p0 - center;
65 from_center.v.atan2(from_center.u)
66 };
67 let end_angle = {
68 let from_center = p1 - center;
69 let offset = if more_than_half_turn {
70 Scalar::TAU
71 } else {
72 Scalar::ZERO
73 };
74
75 from_center.v.atan2(from_center.u) + offset
76 };
77 Self {
78 center,
79 radius,
80 start_angle,
81 end_angle,
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use crate::{Point, Scalar, Vector};
89
90 use super::Arc;
91
92 use approx::{assert_abs_diff_eq, AbsDiffEq};
93
94 #[test]
95 fn arc_construction() {
96 check_arc_calculation(
97 [0., 0.],
98 1.,
99 0_f64.to_radians(),
100 90_f64.to_radians(),
101 );
102 check_arc_calculation(
103 [-4., 2.],
104 1.5,
105 5_f64.to_radians(),
106 -5_f64.to_radians(),
107 );
108 check_arc_calculation(
109 [3., 8.],
110 3.,
111 0_f64.to_radians(),
112 100_f64.to_radians(),
113 );
114 check_arc_calculation(
115 [1., -1.],
116 1.,
117 90_f64.to_radians(),
118 180_f64.to_radians(),
119 );
120 check_arc_calculation(
121 [0., 0.],
122 1.,
123 0_f64.to_radians(),
124 270_f64.to_radians(),
125 );
126 }
127
128 fn check_arc_calculation(
129 center: impl Into<Point<2>>,
130 radius: f64,
131 a0: f64,
132 a1: f64,
133 ) {
134 let center = center.into();
135 let angle = a1 - a0;
136
137 let p0 = center + Vector::from([a0.cos(), a0.sin()]) * radius;
138 let p1 = center + Vector::from([a1.cos(), a1.sin()]) * radius;
139
140 let arc = Arc::from_endpoints_and_angle(p0, p1, Scalar::from(angle));
141
142 let epsilon = Scalar::default_epsilon() * 10.;
143
144 dbg!(arc.start_angle);
145 dbg!(arc.end_angle);
146 assert_abs_diff_eq!(arc.center, center, epsilon = epsilon);
147 assert_abs_diff_eq!(
148 arc.radius,
149 Scalar::from(radius),
150 epsilon = epsilon
151 );
152
153 assert_abs_diff_eq!(
154 arc.start_angle,
155 Scalar::from(a0),
156 epsilon = epsilon
157 );
158 assert_abs_diff_eq!(arc.end_angle, Scalar::from(a1), epsilon = epsilon);
159 }
160}