1use std::f64::consts::PI;
2use std::f64::consts::TAU;
3
4use kittycad_modeling_cmds::shared::Angle;
5use kittycad_modeling_cmds::units::UnitLength;
6
7use super::args::TyF64;
8use crate::execution::types::NumericType;
9use crate::util::MathExt;
10
11pub(crate) fn untype_point(p: [TyF64; 2]) -> ([f64; 2], NumericType) {
12 let (x, y, ty) = NumericType::combine_eq_coerce(p[0].clone(), p[1].clone(), None);
13 ([x, y], ty)
14}
15
16pub(crate) fn untype_array<const N: usize>(p: [TyF64; N]) -> ([f64; N], NumericType) {
17 let (vec, ty) = NumericType::combine_eq_array(&p);
18 (
19 vec.try_into()
20 .unwrap_or_else(|v: Vec<f64>| panic!("Expected a Vec of length {} but it was {}", N, v.len())),
21 ty,
22 )
23}
24
25pub(crate) fn point_to_mm(p: [TyF64; 2]) -> [f64; 2] {
26 [p[0].to_mm(), p[1].to_mm()]
27}
28
29pub(crate) fn untyped_point_to_mm(p: [f64; 2], units: UnitLength) -> [f64; 2] {
30 untyped_point_to_unit(p, units, UnitLength::Millimeters)
31}
32
33pub fn untyped_point_to_unit(point: [f64; 2], from_len_unit: UnitLength, to_len_unit: UnitLength) -> [f64; 2] {
34 [
35 crate::execution::types::adjust_length(from_len_unit, point[0], to_len_unit).0,
36 crate::execution::types::adjust_length(from_len_unit, point[1], to_len_unit).0,
37 ]
38}
39
40pub(crate) fn point_to_len_unit(p: [TyF64; 2], len: UnitLength) -> [f64; 2] {
41 [p[0].to_length_units(len), p[1].to_length_units(len)]
42}
43
44pub(crate) fn point_to_typed(p: [f64; 2], len: UnitLength) -> [TyF64; 2] {
46 [TyF64::new(p[0], len.into()), TyF64::new(p[1], len.into())]
47}
48
49pub(crate) fn point_3d_to_mm(p: [TyF64; 3]) -> [f64; 3] {
50 [p[0].to_mm(), p[1].to_mm(), p[2].to_mm()]
51}
52
53pub(crate) fn distance(a: Coords2d, b: Coords2d) -> f64 {
55 ((b[0] - a[0]).squared() + (b[1] - a[1]).squared()).sqrt()
56}
57
58pub(crate) fn between(a: Coords2d, b: Coords2d) -> Angle {
60 let x = b[0] - a[0];
61 let y = b[1] - a[1];
62 normalize(Angle::from_radians(libm::atan2(y, x)))
63}
64
65pub(crate) fn normalize(angle: Angle) -> Angle {
67 let deg = angle.to_degrees();
68 let result = ((deg % 360.0) + 360.0) % 360.0;
69 Angle::from_degrees(if result > 180.0 { result - 360.0 } else { result })
70}
71
72pub(crate) fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
88 let norm_from_angle = normalize_rad(from_angle.to_radians());
89 let norm_to_angle = normalize_rad(to_angle.to_radians());
90 let provisional = norm_to_angle - norm_from_angle;
91
92 if provisional > -PI && provisional <= PI {
93 return Angle::from_radians(provisional);
94 }
95 if provisional > PI {
96 return Angle::from_radians(provisional - TAU);
97 }
98 if provisional < -PI {
99 return Angle::from_radians(provisional + TAU);
100 }
101 Angle::default()
102}
103
104pub(crate) fn normalize_rad(angle: f64) -> f64 {
105 let draft = angle % (TAU);
106 if draft < 0.0 { draft + TAU } else { draft }
107}
108
109fn calculate_intersection_of_two_lines(line1: &[Coords2d; 2], line2_angle: f64, line2_point: Coords2d) -> Coords2d {
110 let line2_point_b = [
111 line2_point[0] + libm::cos(line2_angle.to_radians()) * 10.0,
112 line2_point[1] + libm::sin(line2_angle.to_radians()) * 10.0,
113 ];
114 intersect(line1[0], line1[1], line2_point, line2_point_b)
115}
116
117fn intersect(p1: Coords2d, p2: Coords2d, p3: Coords2d, p4: Coords2d) -> Coords2d {
118 let slope = |p1: Coords2d, p2: Coords2d| (p1[1] - p2[1]) / (p1[0] - p2[0]);
119 let constant = |p1: Coords2d, p2: Coords2d| p1[1] - slope(p1, p2) * p1[0];
120 let get_y = |for_x: f64, p1: Coords2d, p2: Coords2d| slope(p1, p2) * for_x + constant(p1, p2);
121
122 if p1[0] == p2[0] {
123 return [p1[0], get_y(p1[0], p3, p4)];
124 }
125 if p3[0] == p4[0] {
126 return [p3[0], get_y(p3[0], p1, p2)];
127 }
128
129 let x = (constant(p3, p4) - constant(p1, p2)) / (slope(p1, p2) - slope(p3, p4));
130 let y = get_y(x, p1, p2);
131 [x, y]
132}
133
134pub(crate) fn intersection_with_parallel_line(
135 line1: &[Coords2d; 2],
136 line1_offset: f64,
137 line2_angle: f64,
138 line2_point: Coords2d,
139) -> Coords2d {
140 calculate_intersection_of_two_lines(&offset_line(line1_offset, line1[0], line1[1]), line2_angle, line2_point)
141}
142
143fn offset_line(offset: f64, p1: Coords2d, p2: Coords2d) -> [Coords2d; 2] {
144 if p1[0] == p2[0] {
145 let direction = (p1[1] - p2[1]).signum();
146 return [[p1[0] + offset * direction, p1[1]], [p2[0] + offset * direction, p2[1]]];
147 }
148 if p1[1] == p2[1] {
149 let direction = (p2[0] - p1[0]).signum();
150 return [[p1[0], p1[1] + offset * direction], [p2[0], p2[1] + offset * direction]];
151 }
152 let x_offset = offset / libm::sin(libm::atan2(p1[1] - p2[1], p1[0] - p2[0]));
153 [[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]]
154}
155
156pub(crate) fn get_y_component(angle: Angle, x: f64) -> Coords2d {
157 let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; let y = x * libm::tan(normalised_angle.to_radians());
159 let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
160 -1.0
161 } else {
162 1.0
163 };
164 [x * sign, y * sign]
165}
166
167pub(crate) fn get_x_component(angle: Angle, y: f64) -> Coords2d {
168 let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; let x = y / libm::tan(normalised_angle.to_radians());
170 let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
171 -1.0
172 } else {
173 1.0
174 };
175 [x * sign, y * sign]
176}
177
178pub(crate) fn arc_center_and_end(
179 from: Coords2d,
180 start_angle: Angle,
181 end_angle: Angle,
182 radius: f64,
183) -> (Coords2d, Coords2d) {
184 let start_angle = start_angle.to_radians();
185 let end_angle = end_angle.to_radians();
186
187 let center = [
188 -(radius * libm::cos(start_angle) - from[0]),
189 -(radius * libm::sin(start_angle) - from[1]),
190 ];
191
192 let end = [
193 center[0] + radius * libm::cos(end_angle),
194 center[1] + radius * libm::sin(end_angle),
195 ];
196
197 (center, end)
198}
199
200pub(crate) fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
203 let (x1, y1) = (p1[0], p1[1]);
204 let (x2, y2) = (p2[0], p2[1]);
205 let (x3, y3) = (p3[0], p3[1]);
206
207 let d = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
211
212 if d.abs() < f64::EPSILON {
214 return [(x1 + x2 + x3) / 3.0, (y1 + y2 + y3) / 3.0];
215 }
216
217 let p1_sq = x1 * x1 + y1 * y1;
219 let p2_sq = x2 * x2 + y2 * y2;
220 let p3_sq = x3 * x3 + y3 * y3;
221
222 [
228 (p1_sq * (y2 - y3) + p2_sq * (y3 - y1) + p3_sq * (y1 - y2)) / d,
229 (p1_sq * (x3 - x2) + p2_sq * (x1 - x3) + p3_sq * (x2 - x1)) / d,
230 ]
231}
232
233pub struct CircleParams {
234 pub center: Coords2d,
235 pub radius: f64,
236}
237
238pub fn calculate_circle_from_3_points(points: [Coords2d; 3]) -> CircleParams {
239 let center = calculate_circle_center(points[0], points[1], points[2]);
240 CircleParams {
241 center,
242 radius: distance(center, points[1]),
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use std::f64::consts::TAU;
250
251 use approx::assert_relative_eq;
252 use pretty_assertions::assert_eq;
253
254 use super::Angle;
255 use super::calculate_circle_center;
256 use super::get_x_component;
257 use super::get_y_component;
258 use crate::util::MathExt;
259
260 static EACH_QUAD: [(i32, [i32; 2]); 12] = [
261 (-315, [1, 1]),
262 (-225, [-1, 1]),
263 (-135, [-1, -1]),
264 (-45, [1, -1]),
265 (45, [1, 1]),
266 (135, [-1, 1]),
267 (225, [-1, -1]),
268 (315, [1, -1]),
269 (405, [1, 1]),
270 (495, [-1, 1]),
271 (585, [-1, -1]),
272 (675, [1, -1]),
273 ];
274
275 #[test]
276 fn test_get_y_component() {
277 let mut expected = Vec::new();
278 let mut results = Vec::new();
279
280 for &(angle, expected_result) in EACH_QUAD.iter() {
281 let res = get_y_component(Angle::from_degrees(angle as f64), 1.0);
282 results.push([res[0].round() as i32, res[1].round() as i32]);
283 expected.push(expected_result);
284 }
285
286 assert_eq!(results, expected);
287
288 let result = get_y_component(Angle::zero(), 1.0);
289 assert_eq!(result[0] as i32, 1);
290 assert_eq!(result[1] as i32, 0);
291
292 let result = get_y_component(Angle::from_degrees(90.0), 1.0);
293 assert_eq!(result[0] as i32, 1);
294 assert!(result[1] > 100000.0);
295
296 let result = get_y_component(Angle::from_degrees(180.0), 1.0);
297 assert_eq!(result[0] as i32, -1);
298 assert!((result[1] - 0.0).abs() < f64::EPSILON);
299
300 let result = get_y_component(Angle::from_degrees(270.0), 1.0);
301 assert_eq!(result[0] as i32, -1);
302 assert!(result[1] < -100000.0);
303 }
304
305 #[test]
306 fn test_get_x_component() {
307 let mut expected = Vec::new();
308 let mut results = Vec::new();
309
310 for &(angle, expected_result) in EACH_QUAD.iter() {
311 let res = get_x_component(Angle::from_degrees(angle as f64), 1.0);
312 results.push([res[0].round() as i32, res[1].round() as i32]);
313 expected.push(expected_result);
314 }
315
316 assert_eq!(results, expected);
317
318 let result = get_x_component(Angle::zero(), 1.0);
319 assert!(result[0] > 100000.0);
320 assert_eq!(result[1] as i32, 1);
321
322 let result = get_x_component(Angle::from_degrees(90.0), 1.0);
323 assert!((result[0] - 0.0).abs() < f64::EPSILON);
324 assert_eq!(result[1] as i32, 1);
325
326 let result = get_x_component(Angle::from_degrees(180.0), 1.0);
327 assert!(result[0] < -100000.0);
328 assert_eq!(result[1] as i32, 1);
329
330 let result = get_x_component(Angle::from_degrees(270.0), 1.0);
331 assert!((result[0] - 0.0).abs() < f64::EPSILON);
332 assert_eq!(result[1] as i32, -1);
333 }
334
335 #[test]
336 fn test_arc_center_and_end() {
337 let (center, end) = super::arc_center_and_end([0.0, 0.0], Angle::zero(), Angle::from_degrees(90.0), 1.0);
338 assert_eq!(center[0].round(), -1.0);
339 assert_eq!(center[1], 0.0);
340 assert_eq!(end[0].round(), -1.0);
341 assert_eq!(end[1], 1.0);
342
343 let (center, end) = super::arc_center_and_end([0.0, 0.0], Angle::zero(), Angle::from_degrees(180.0), 1.0);
344 assert_eq!(center[0].round(), -1.0);
345 assert_eq!(center[1], 0.0);
346 assert_eq!(end[0].round(), -2.0);
347 assert_eq!(end[1].round(), 0.0);
348
349 let (center, end) = super::arc_center_and_end([0.0, 0.0], Angle::zero(), Angle::from_degrees(180.0), 10.0);
350 assert_eq!(center[0].round(), -10.0);
351 assert_eq!(center[1], 0.0);
352 assert_eq!(end[0].round(), -20.0);
353 assert_eq!(end[1].round(), 0.0);
354 }
355
356 #[test]
357 fn test_calculate_circle_center() {
358 const EPS: f64 = 1e-4;
359
360 let p1 = [1.0, 2.0];
362 let p2 = [4.0, 5.0];
363 let p3 = [7.0, 3.0];
364 let center = calculate_circle_center(p1, p2, p3);
365 assert_relative_eq!(center[0], 4.1, epsilon = EPS);
366 assert_relative_eq!(center[1], 1.9, epsilon = EPS);
367
368 let center = [3.2, 0.7];
370 let radius_array = [0.001, 0.01, 0.6, 1.0, 5.0, 60.0, 500.0, 2000.0, 400_000.0];
371 let points_array = [[0.0, 0.33, 0.66], [0.0, 0.1, 0.2], [0.0, -0.1, 0.1], [0.0, 0.5, 0.7]];
372
373 let get_point = |radius: f64, t: f64| {
374 let angle = t * TAU;
375 [
376 center[0] + radius * libm::cos(angle),
377 center[1] + radius * libm::sin(angle),
378 ]
379 };
380
381 for radius in radius_array {
382 for point in points_array {
383 let p1 = get_point(radius, point[0]);
384 let p2 = get_point(radius, point[1]);
385 let p3 = get_point(radius, point[2]);
386 let c = calculate_circle_center(p1, p2, p3);
387 assert_relative_eq!(c[0], center[0], epsilon = EPS);
388 assert_relative_eq!(c[1], center[1], epsilon = EPS);
389 }
390 }
391
392 let p1 = [0.0, 0.0];
394 let p2 = [1.0, 0.0];
395 let p3 = [0.5, 3.0_f64.sqrt() / 2.0];
396 let center = calculate_circle_center(p1, p2, p3);
397 assert_relative_eq!(center[0], 0.5, epsilon = EPS);
398 assert_relative_eq!(center[1], 1.0 / (2.0 * 3.0_f64.sqrt()), epsilon = EPS);
399
400 let p1 = [0.0, 0.0];
402 let p2 = [1.0, 0.0];
403 let p3 = [2.0, 0.0];
404 let center = calculate_circle_center(p1, p2, p3);
405 assert_relative_eq!(center[0], 1.0, epsilon = EPS);
406 assert_relative_eq!(center[1], 0.0, epsilon = EPS);
407
408 let p1 = [0.0, 0.0];
410 let p2 = [0.0, 2.0];
411 let p3 = [2.0, 0.0];
412 let center = calculate_circle_center(p1, p2, p3);
413 assert_relative_eq!(center[0], 1.0, epsilon = EPS);
414 assert_relative_eq!(center[1], 1.0, epsilon = EPS);
415
416 let p1 = [0.0, 0.0];
418 let p2 = [0.0, 6.0];
419 let p3 = [6.0, 0.0];
420 let center = calculate_circle_center(p1, p2, p3);
421 assert_relative_eq!(center[0], 3.0, epsilon = EPS);
422 assert_relative_eq!(center[1], 3.0, epsilon = EPS);
423 let radius = ((center[0] - p1[0]).squared() + (center[1] - p1[1]).squared()).sqrt();
425 assert_relative_eq!(radius, 3.0 * 2.0_f64.sqrt(), epsilon = EPS);
426 }
427}
428
429pub(crate) type Coords2d = [f64; 2];
430
431pub fn is_points_ccw_wasm(points: &[f64]) -> i32 {
432 let mut sum = 0.0;
435 for i in 0..(points.len() / 2) {
436 let point1 = [points[2 * i], points[2 * i + 1]];
437 let point2 = [points[(2 * i + 2) % points.len()], points[(2 * i + 3) % points.len()]];
438 sum += (point2[0] + point1[0]) * (point2[1] - point1[1]);
439 }
440 sum.signum() as i32
441}
442
443pub(crate) fn is_points_ccw(points: &[Coords2d]) -> i32 {
444 let flattened_points: Vec<f64> = points.iter().flat_map(|&p| vec![p[0], p[1]]).collect();
445 is_points_ccw_wasm(&flattened_points)
446}
447
448fn get_slope(start: Coords2d, end: Coords2d) -> (f64, f64) {
449 let slope = if start[0] - end[0] == 0.0 {
450 f64::INFINITY
451 } else {
452 (start[1] - end[1]) / (start[0] - end[0])
453 };
454
455 let perp_slope = if slope == f64::INFINITY { 0.0 } else { -1.0 / slope };
456
457 (slope, perp_slope)
458}
459
460fn get_angle(point1: Coords2d, point2: Coords2d) -> f64 {
461 let delta_x = point2[0] - point1[0];
462 let delta_y = point2[1] - point1[1];
463 let angle = libm::atan2(delta_y, delta_x);
464
465 let result = if angle < 0.0 { angle + TAU } else { angle };
466 result * (180.0 / PI)
467}
468
469fn delta_angle(from_angle: f64, to_angle: f64) -> f64 {
470 let norm_from_angle = normalize_rad(from_angle);
471 let norm_to_angle = normalize_rad(to_angle);
472 let provisional = norm_to_angle - norm_from_angle;
473
474 if provisional > -PI && provisional <= PI {
475 provisional
476 } else if provisional > PI {
477 provisional - TAU
478 } else if provisional < -PI {
479 provisional + TAU
480 } else {
481 provisional
482 }
483}
484
485fn deg2rad(deg: f64) -> f64 {
486 deg * (PI / 180.0)
487}
488
489fn get_mid_point(
490 center: Coords2d,
491 arc_start_point: Coords2d,
492 arc_end_point: Coords2d,
493 tan_previous_point: Coords2d,
494 radius: f64,
495 obtuse: bool,
496) -> Coords2d {
497 let angle_from_center_to_arc_start = get_angle(center, arc_start_point);
498 let angle_from_center_to_arc_end = get_angle(center, arc_end_point);
499 let delta_ang = delta_angle(
500 deg2rad(angle_from_center_to_arc_start),
501 deg2rad(angle_from_center_to_arc_end),
502 );
503 let delta_ang = delta_ang / 2.0 + deg2rad(angle_from_center_to_arc_start);
504 let shortest_arc_mid_point: Coords2d = [
505 libm::cos(delta_ang) * radius + center[0],
506 libm::sin(delta_ang) * radius + center[1],
507 ];
508 let opposite_delta = delta_ang + PI;
509 let longest_arc_mid_point: Coords2d = [
510 libm::cos(opposite_delta) * radius + center[0],
511 libm::sin(opposite_delta) * radius + center[1],
512 ];
513
514 let rotation_direction_original_points = is_points_ccw(&[tan_previous_point, arc_start_point, arc_end_point]);
515 let rotation_direction_points_on_arc = is_points_ccw(&[arc_start_point, shortest_arc_mid_point, arc_end_point]);
516 if rotation_direction_original_points != rotation_direction_points_on_arc && obtuse {
517 longest_arc_mid_point
518 } else {
519 shortest_arc_mid_point
520 }
521}
522
523fn intersect_point_n_slope(point1: Coords2d, slope1: f64, point2: Coords2d, slope2: f64) -> Coords2d {
524 let x = if slope1.abs() == f64::INFINITY {
525 point1[0]
526 } else if slope2.abs() == f64::INFINITY {
527 point2[0]
528 } else {
529 (point2[1] - slope2 * point2[0] - point1[1] + slope1 * point1[0]) / (slope1 - slope2)
530 };
531 let y = if slope1.abs() != f64::INFINITY {
532 slope1 * x - slope1 * point1[0] + point1[1]
533 } else {
534 slope2 * x - slope2 * point2[0] + point2[1]
535 };
536 [x, y]
537}
538
539pub struct TangentialArcInfoInput {
541 pub arc_start_point: Coords2d,
543 pub arc_end_point: Coords2d,
545 pub tan_previous_point: Coords2d,
547 pub obtuse: bool,
549}
550
551pub struct TangentialArcInfoOutput {
553 pub center: Coords2d,
555 pub arc_mid_point: Coords2d,
557 pub radius: f64,
559 pub start_angle: f64,
561 pub end_angle: f64,
563 pub ccw: i32,
565 pub arc_length: f64,
567}
568
569pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialArcInfoOutput {
573 let (_, perp_slope) = get_slope(input.tan_previous_point, input.arc_start_point);
574 let tangential_line_perp_slope = perp_slope;
575
576 let mid_point: Coords2d = [
578 (input.arc_start_point[0] + input.arc_end_point[0]) / 2.0,
579 (input.arc_start_point[1] + input.arc_end_point[1]) / 2.0,
580 ];
581
582 let slope_mid_point_line = get_slope(input.arc_start_point, mid_point);
583
584 let center: Coords2d;
585 let radius: f64;
586
587 if tangential_line_perp_slope == slope_mid_point_line.0 {
588 center = mid_point;
591 radius = ((input.arc_start_point[0] - center[0]).squared() + (input.arc_start_point[1] - center[1]).squared())
592 .sqrt();
593 } else {
594 center = intersect_point_n_slope(
595 mid_point,
596 slope_mid_point_line.1,
597 input.arc_start_point,
598 tangential_line_perp_slope,
599 );
600 radius = ((input.arc_start_point[0] - center[0]).squared() + (input.arc_start_point[1] - center[1]).squared())
601 .sqrt();
602 }
603
604 let arc_mid_point = get_mid_point(
605 center,
606 input.arc_start_point,
607 input.arc_end_point,
608 input.tan_previous_point,
609 radius,
610 input.obtuse,
611 );
612
613 let start_angle = libm::atan2(
614 input.arc_start_point[1] - center[1],
615 input.arc_start_point[0] - center[0],
616 );
617 let end_angle = libm::atan2(input.arc_end_point[1] - center[1], input.arc_end_point[0] - center[0]);
618 let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]);
619
620 let arc_mid_angle = libm::atan2(arc_mid_point[1] - center[1], arc_mid_point[0] - center[0]);
621 let start_to_mid_arc_length = radius
622 * delta(Angle::from_radians(start_angle), Angle::from_radians(arc_mid_angle))
623 .to_radians()
624 .abs();
625 let mid_to_end_arc_length = radius
626 * delta(Angle::from_radians(arc_mid_angle), Angle::from_radians(end_angle))
627 .to_radians()
628 .abs();
629 let arc_length = start_to_mid_arc_length + mid_to_end_arc_length;
630
631 TangentialArcInfoOutput {
632 center,
633 radius,
634 arc_mid_point,
635 start_angle,
636 end_angle,
637 ccw,
638 arc_length,
639 }
640}
641
642#[cfg(test)]
643mod get_tangential_arc_to_info_tests {
644 use approx::assert_relative_eq;
645
646 use super::*;
647
648 fn round_to_three_decimals(num: f64) -> f64 {
649 (num * 1000.0).round() / 1000.0
650 }
651
652 #[test]
653 fn test_basic_case() {
654 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
655 tan_previous_point: [0.0, -5.0],
656 arc_start_point: [0.0, 0.0],
657 arc_end_point: [4.0, 0.0],
658 obtuse: true,
659 });
660 assert_relative_eq!(result.center[0], 2.0);
661 assert_relative_eq!(result.center[1], 0.0);
662 assert_relative_eq!(result.arc_mid_point[0], 2.0);
663 assert_relative_eq!(result.arc_mid_point[1], 2.0);
664 assert_relative_eq!(result.radius, 2.0);
665 assert_relative_eq!(result.start_angle, PI);
666 assert_relative_eq!(result.end_angle, 0.0);
667 assert_eq!(result.ccw, -1);
668 }
669
670 #[test]
671 fn basic_case_with_arc_centered_at_0_0_and_the_tangential_line_being_45_degrees() {
672 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
673 tan_previous_point: [0.0, -4.0],
674 arc_start_point: [2.0, -2.0],
675 arc_end_point: [-2.0, 2.0],
676 obtuse: true,
677 });
678 assert_relative_eq!(result.center[0], 0.0);
679 assert_relative_eq!(result.center[1], 0.0);
680 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[0]), 2.0);
681 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[1]), 2.0);
682 assert_relative_eq!(result.radius, (2.0f64 * 2.0 + 2.0 * 2.0).sqrt());
683 assert_relative_eq!(result.start_angle, -PI / 4.0);
684 assert_relative_eq!(result.end_angle, 3.0 * PI / 4.0);
685 assert_eq!(result.ccw, 1);
686 }
687
688 #[test]
689 fn test_get_tangential_arc_to_info_moving_arc_end_point() {
690 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
691 tan_previous_point: [0.0, -4.0],
692 arc_start_point: [2.0, -2.0],
693 arc_end_point: [2.0, 2.0],
694 obtuse: true,
695 });
696 let expected_radius = (2.0f64 * 2.0 + 2.0 * 2.0).sqrt();
697 assert_relative_eq!(round_to_three_decimals(result.center[0]), 0.0);
698 assert_relative_eq!(result.center[1], 0.0);
699 assert_relative_eq!(result.arc_mid_point[0], expected_radius);
700 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[1]), -0.0);
701 assert_relative_eq!(result.radius, expected_radius);
702 assert_relative_eq!(result.start_angle, -PI / 4.0);
703 assert_relative_eq!(result.end_angle, PI / 4.0);
704 assert_eq!(result.ccw, 1);
705 }
706
707 #[test]
708 fn test_get_tangential_arc_to_info_moving_arc_end_point_again() {
709 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
710 tan_previous_point: [0.0, -4.0],
711 arc_start_point: [2.0, -2.0],
712 arc_end_point: [-2.0, -2.0],
713 obtuse: true,
714 });
715 let expected_radius = (2.0f64 * 2.0 + 2.0 * 2.0).sqrt();
716 assert_relative_eq!(result.center[0], 0.0);
717 assert_relative_eq!(result.center[1], 0.0);
718 assert_relative_eq!(result.radius, expected_radius);
719 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[0]), 0.0);
720 assert_relative_eq!(result.arc_mid_point[1], expected_radius);
721 assert_relative_eq!(result.start_angle, -PI / 4.0);
722 assert_relative_eq!(result.end_angle, -3.0 * PI / 4.0);
723 assert_eq!(result.ccw, 1);
724 }
725
726 #[test]
727 fn test_get_tangential_arc_to_info_acute_moving_arc_end_point() {
728 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
729 tan_previous_point: [0.0, -4.0],
730 arc_start_point: [2.0, -2.0],
731 arc_end_point: [-2.0, -2.0],
732 obtuse: false,
733 });
734 let expected_radius = (2.0f64 * 2.0 + 2.0 * 2.0).sqrt();
735 assert_relative_eq!(result.center[0], 0.0);
736 assert_relative_eq!(result.center[1], 0.0);
737 assert_relative_eq!(result.radius, expected_radius);
738 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[0]), -0.0);
739 assert_relative_eq!(result.arc_mid_point[1], -expected_radius);
740 assert_relative_eq!(result.start_angle, -PI / 4.0);
741 assert_relative_eq!(result.end_angle, -3.0 * PI / 4.0);
742 assert_eq!(result.ccw, -1);
744 }
745
746 #[test]
747 fn test_get_tangential_arc_to_info_obtuse_with_wrap_around() {
748 let arc_end = libm::cos(std::f64::consts::PI / 4.0) * 2.0;
749 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
750 tan_previous_point: [2.0, -4.0],
751 arc_start_point: [2.0, 0.0],
752 arc_end_point: [0.0, -2.0],
753 obtuse: true,
754 });
755 assert_relative_eq!(result.center[0], -0.0);
756 assert_relative_eq!(result.center[1], 0.0);
757 assert_relative_eq!(result.radius, 2.0);
758 assert_relative_eq!(result.arc_mid_point[0], -arc_end);
759 assert_relative_eq!(result.arc_mid_point[1], arc_end);
760 assert_relative_eq!(result.start_angle, 0.0);
761 assert_relative_eq!(result.end_angle, -PI / 2.0);
762 assert_eq!(result.ccw, 1);
763 }
764
765 #[test]
766 fn test_arc_length_obtuse_cw() {
767 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
768 tan_previous_point: [-1.0, -1.0],
769 arc_start_point: [-1.0, 0.0],
770 arc_end_point: [0.0, -1.0],
771 obtuse: true,
772 });
773 let circumference = TAU * result.radius;
774 let expected_length = circumference * 3.0 / 4.0; assert_relative_eq!(result.arc_length, expected_length);
776 }
777
778 #[test]
779 fn test_arc_length_acute_cw() {
780 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
781 tan_previous_point: [-1.0, -1.0],
782 arc_start_point: [-1.0, 0.0],
783 arc_end_point: [0.0, 1.0],
784 obtuse: true,
785 });
786 let circumference = TAU * result.radius;
787 let expected_length = circumference / 4.0; assert_relative_eq!(result.arc_length, expected_length);
789 }
790
791 #[test]
792 fn test_arc_length_obtuse_ccw() {
793 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
794 tan_previous_point: [1.0, -1.0],
795 arc_start_point: [1.0, 0.0],
796 arc_end_point: [0.0, -1.0],
797 obtuse: true,
798 });
799 let circumference = TAU * result.radius;
800 let expected_length = circumference * 3.0 / 4.0; assert_relative_eq!(result.arc_length, expected_length);
802 }
803
804 #[test]
805 fn test_arc_length_acute_ccw() {
806 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
807 tan_previous_point: [1.0, -1.0],
808 arc_start_point: [1.0, 0.0],
809 arc_end_point: [0.0, 1.0],
810 obtuse: true,
811 });
812 let circumference = TAU * result.radius;
813 let expected_length = circumference / 4.0; assert_relative_eq!(result.arc_length, expected_length);
815 }
816}
817
818pub(crate) fn get_tangent_point_from_previous_arc(
819 last_arc_center: Coords2d,
820 last_arc_ccw: bool,
821 last_arc_end: Coords2d,
822) -> Coords2d {
823 let angle_from_old_center_to_arc_start = get_angle(last_arc_center, last_arc_end);
824 let tangential_angle = angle_from_old_center_to_arc_start + if last_arc_ccw { -90.0 } else { 90.0 };
825 [
827 libm::cos(tangential_angle.to_radians()) * 10.0 + last_arc_end[0],
828 libm::sin(tangential_angle.to_radians()) * 10.0 + last_arc_end[1],
829 ]
830}