1use crate::plain::{P2, add, distance, dot, perp, rotate, scale, sub, unit};
12
13#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum Measure {
16 Linear { a: P2, b: P2, along: P2 },
19 Radial {
21 center: P2,
22 radius: f64,
23 diameter: bool,
24 },
25 Angular {
29 vertex: P2,
30 from: P2,
31 to: P2,
32 sweep: f64,
33 },
34}
35
36const ARC_PIECES: usize = 32;
38
39impl Measure {
40 pub fn lines(&self, label: P2) -> Vec<Vec<P2>> {
49 match *self {
50 Measure::Linear { a, b, along } => {
51 let Some((a2, b2, u)) = Self::feet(a, b, along, label) else {
52 return Vec::new();
53 };
54 let t = |q: P2| dot(sub(q, a2), u);
57 let (lo, hi) = (t(b2).min(0.0).min(t(label)), t(b2).max(0.0).max(t(label)));
58 vec![
59 vec![a, a2],
60 vec![b, b2],
61 vec![add(a2, scale(u, lo)), add(a2, scale(u, hi))],
62 ]
63 }
64 Measure::Radial {
65 center,
66 radius,
67 diameter,
68 } => {
69 let Some(d) = unit(sub(label, center)) else {
70 return Vec::new();
71 };
72 let rim = add(center, scale(d, radius));
73 let from = if diameter {
74 add(center, scale(d, -radius))
75 } else {
76 center
77 };
78 let to = if dot(sub(label, center), d) > radius {
80 label
81 } else {
82 rim
83 };
84 vec![vec![from, to]]
85 }
86 Measure::Angular {
87 vertex, from, to, ..
88 } => {
89 let r = distance(label, vertex);
90 let side = |d: P2| vec![vertex, add(vertex, scale(unit(d).unwrap_or(d), r))];
91 vec![self.arc(r), side(from), side(to)]
92 }
93 }
94 }
95
96 pub fn arrows(&self, label: P2) -> Vec<(P2, P2)> {
102 match *self {
103 Measure::Linear { a, b, along, .. } => {
104 let Some((a2, b2, _)) = Self::feet(a, b, along, label) else {
105 return Vec::new();
106 };
107 match unit(sub(b2, a2)) {
108 Some(d) => vec![(a2, scale(d, -1.0)), (b2, d)],
109 None => Vec::new(),
110 }
111 }
112 Measure::Radial {
113 center,
114 radius,
115 diameter,
116 } => {
117 let Some(d) = unit(sub(label, center)) else {
118 return Vec::new();
119 };
120 let mut arrows = vec![(add(center, scale(d, radius)), d)];
121 if diameter {
122 arrows.push((add(center, scale(d, -radius)), scale(d, -1.0)));
123 }
124 arrows
125 }
126 Measure::Angular {
127 vertex,
128 from,
129 sweep,
130 ..
131 } => {
132 let r = distance(label, vertex);
133 let (Some(from), true) = (unit(from), r > 0.0 && sweep != 0.0) else {
134 return Vec::new();
135 };
136 let arc = self.arc(r);
137 let turn = sweep.signum();
138 let end = rotate(from, sweep);
139 vec![
140 (arc[0], scale(perp(from), -turn)),
141 (arc[arc.len() - 1], scale(perp(end), turn)),
142 ]
143 }
144 }
145 }
146
147 fn feet(a: P2, b: P2, along: P2, label: P2) -> Option<(P2, P2, P2)> {
150 let u = unit(along)?;
151 let n = perp(u);
152 let lift = |q: P2| add(q, scale(n, dot(sub(label, q), n)));
153 Some((lift(a), lift(b), u))
154 }
155
156 fn arc(&self, r: f64) -> Vec<P2> {
158 let Measure::Angular {
159 vertex,
160 from,
161 sweep,
162 ..
163 } = *self
164 else {
165 return Vec::new();
166 };
167 let start = from[1].atan2(from[0]);
168 (0..=ARC_PIECES)
169 .map(|i| {
170 let t = start + sweep * i as f64 / ARC_PIECES as f64;
171 add(vertex, [r * t.cos(), r * t.sin()])
172 })
173 .collect()
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 fn near(a: P2, b: P2) -> bool {
182 distance(a, b) < 1e-12
183 }
184
185 #[test]
189 fn a_linear_dimension_reaches_its_label() {
190 let m = Measure::Linear {
191 a: [0.0, 0.0],
192 b: [4.0, 1.0],
193 along: [1.0, 0.0],
194 };
195 let lines = m.lines([6.0, 3.0]);
196 assert_eq!(lines[0], vec![[0.0, 0.0], [0.0, 3.0]]);
197 assert_eq!(lines[1], vec![[4.0, 1.0], [4.0, 3.0]]);
198 assert_eq!(lines[2], vec![[0.0, 3.0], [6.0, 3.0]]);
199 let arrows = m.arrows([6.0, 3.0]);
200 assert_eq!(
201 arrows,
202 vec![([0.0, 3.0], [-1.0, 0.0]), ([4.0, 3.0], [1.0, 0.0])]
203 );
204 }
205
206 #[test]
209 fn radial_dimensions_cross_their_circle() {
210 let diameter = Measure::Radial {
211 center: [1.0, 1.0],
212 radius: 2.0,
213 diameter: true,
214 };
215 assert_eq!(
216 diameter.lines([1.0, 5.0]),
217 vec![vec![[1.0, -1.0], [1.0, 5.0]]]
218 );
219 assert_eq!(diameter.arrows([1.0, 5.0]).len(), 2);
220 let radius = Measure::Radial {
221 center: [1.0, 1.0],
222 radius: 2.0,
223 diameter: false,
224 };
225 assert_eq!(radius.lines([1.0, 2.0]), vec![vec![[1.0, 1.0], [1.0, 3.0]]]);
226 assert_eq!(radius.arrows([1.0, 2.0]), vec![([1.0, 3.0], [0.0, 1.0])]);
227 }
228
229 #[test]
232 fn an_angle_arcs_through_its_label() {
233 let m = Measure::Angular {
234 vertex: [0.0, 0.0],
235 from: [1.0, 0.0],
236 to: [0.0, 1.0],
237 sweep: std::f64::consts::FRAC_PI_2,
238 };
239 let label = [2.0_f64.sqrt(), 2.0_f64.sqrt()];
240 let lines = m.lines(label);
241 assert!(near(lines[0][0], [2.0, 0.0]) && near(*lines[0].last().unwrap(), [0.0, 2.0]));
242 assert!(near(lines[2][1], [0.0, 2.0]));
243 let arrows = m.arrows(label);
244 assert!(near(arrows[0].1, [0.0, -1.0]) && near(arrows[1].1, [-1.0, 0.0]));
245 }
246}