1use std::f64::consts::PI;
2
3use kittycad_modeling_cmds::shared::Angle;
4
5use crate::{
6 errors::{KclError, KclErrorDetails},
7 execution::Point2d,
8 source_range::SourceRange,
9};
10
11pub fn distance(a: Point2d, b: Point2d) -> f64 {
13 ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
14}
15
16pub fn between(a: Point2d, b: Point2d) -> Angle {
18 let x = b.x - a.x;
19 let y = b.y - a.y;
20 normalize(Angle::from_radians(y.atan2(x)))
21}
22
23pub fn normalize(angle: Angle) -> Angle {
25 let deg = angle.to_degrees();
26 let result = ((deg % 360.0) + 360.0) % 360.0;
27 Angle::from_degrees(if result > 180.0 { result - 360.0 } else { result })
28}
29
30pub fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
46 let norm_from_angle = normalize_rad(from_angle.to_radians());
47 let norm_to_angle = normalize_rad(to_angle.to_radians());
48 let provisional = norm_to_angle - norm_from_angle;
49
50 if provisional > -PI && provisional <= PI {
51 return Angle::from_radians(provisional);
52 }
53 if provisional > PI {
54 return Angle::from_radians(provisional - 2.0 * PI);
55 }
56 if provisional < -PI {
57 return Angle::from_radians(provisional + 2.0 * PI);
58 }
59 Angle::default()
60}
61
62pub fn normalize_rad(angle: f64) -> f64 {
63 let draft = angle % (2.0 * PI);
64 if draft < 0.0 {
65 draft + 2.0 * PI
66 } else {
67 draft
68 }
69}
70
71pub fn calculate_intersection_of_two_lines(line1: &[Point2d; 2], line2_angle: f64, line2_point: Point2d) -> Point2d {
72 let line2_point_b = Point2d {
73 x: line2_point.x + f64::cos(line2_angle.to_radians()) * 10.0,
74 y: line2_point.y + f64::sin(line2_angle.to_radians()) * 10.0,
75 };
76 intersect(line1[0], line1[1], line2_point, line2_point_b)
77}
78
79pub fn intersect(p1: Point2d, p2: Point2d, p3: Point2d, p4: Point2d) -> Point2d {
80 let slope = |p1: Point2d, p2: Point2d| (p1.y - p2.y) / (p1.x - p2.x);
81 let constant = |p1: Point2d, p2: Point2d| p1.y - slope(p1, p2) * p1.x;
82 let get_y = |for_x: f64, p1: Point2d, p2: Point2d| slope(p1, p2) * for_x + constant(p1, p2);
83
84 if p1.x == p2.x {
85 return Point2d {
86 x: p1.x,
87 y: get_y(p1.x, p3, p4),
88 };
89 }
90 if p3.x == p4.x {
91 return Point2d {
92 x: p3.x,
93 y: get_y(p3.x, p1, p2),
94 };
95 }
96
97 let x = (constant(p3, p4) - constant(p1, p2)) / (slope(p1, p2) - slope(p3, p4));
98 let y = get_y(x, p1, p2);
99 Point2d { x, y }
100}
101
102pub fn intersection_with_parallel_line(
103 line1: &[Point2d; 2],
104 line1_offset: f64,
105 line2_angle: f64,
106 line2_point: Point2d,
107) -> Point2d {
108 calculate_intersection_of_two_lines(&offset_line(line1_offset, line1[0], line1[1]), line2_angle, line2_point)
109}
110
111fn offset_line(offset: f64, p1: Point2d, p2: Point2d) -> [Point2d; 2] {
112 if p1.x == p2.x {
113 let direction = (p1.y - p2.y).signum();
114 return [
115 Point2d {
116 x: p1.x + offset * direction,
117 y: p1.y,
118 },
119 Point2d {
120 x: p2.x + offset * direction,
121 y: p2.y,
122 },
123 ];
124 }
125 if p1.y == p2.y {
126 let direction = (p2.x - p1.x).signum();
127 return [
128 Point2d {
129 x: p1.x,
130 y: p1.y + offset * direction,
131 },
132 Point2d {
133 x: p2.x,
134 y: p2.y + offset * direction,
135 },
136 ];
137 }
138 let x_offset = offset / f64::sin(f64::atan2(p1.y - p2.y, p1.x - p2.x));
139 [
140 Point2d {
141 x: p1.x + x_offset,
142 y: p1.y,
143 },
144 Point2d {
145 x: p2.x + x_offset,
146 y: p2.y,
147 },
148 ]
149}
150
151pub fn get_y_component(angle: Angle, x: f64) -> Point2d {
152 let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; let y = x * f64::tan(normalised_angle.to_radians());
154 let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
155 -1.0
156 } else {
157 1.0
158 };
159 Point2d { x, y }.scale(sign)
160}
161
162pub fn get_x_component(angle: Angle, y: f64) -> Point2d {
163 let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; let x = y / f64::tan(normalised_angle.to_radians());
165 let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
166 -1.0
167 } else {
168 1.0
169 };
170 Point2d { x, y }.scale(sign)
171}
172
173pub fn arc_center_and_end(from: Point2d, start_angle: Angle, end_angle: Angle, radius: f64) -> (Point2d, Point2d) {
174 let start_angle = start_angle.to_radians();
175 let end_angle = end_angle.to_radians();
176
177 let center = Point2d {
178 x: -1.0 * (radius * start_angle.cos() - from.x),
179 y: -1.0 * (radius * start_angle.sin() - from.y),
180 };
181
182 let end = Point2d {
183 x: center.x + radius * end_angle.cos(),
184 y: center.y + radius * end_angle.sin(),
185 };
186
187 (center, end)
188}
189
190pub fn arc_angles(
191 from: Point2d,
192 to: Point2d,
193 center: Point2d,
194 radius: f64,
195 source_range: SourceRange,
196) -> Result<(Angle, Angle), KclError> {
197 if !is_on_circumference(center, from, radius) {
200 return Err(KclError::Semantic(KclErrorDetails {
201 message: format!(
202 "Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
203 from, center, radius
204 ),
205 source_ranges: vec![source_range],
206 }));
207 }
208
209 if !is_on_circumference(center, to, radius) {
210 return Err(KclError::Semantic(KclErrorDetails {
211 message: format!(
212 "Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
213 to, center, radius
214 ),
215 source_ranges: vec![source_range],
216 }));
217 }
218
219 let start_angle = (from.y - center.y).atan2(from.x - center.x);
220 let end_angle = (to.y - center.y).atan2(to.x - center.x);
221
222 Ok((Angle::from_radians(start_angle), Angle::from_radians(end_angle)))
223}
224
225pub fn is_on_circumference(center: Point2d, point: Point2d, radius: f64) -> bool {
226 let dx = point.x - center.x;
227 let dy = point.y - center.y;
228
229 let distance_squared = dx.powi(2) + dy.powi(2);
230
231 (distance_squared - radius.powi(2)).abs() < 1e-9
235}
236
237pub fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
241 let y_2_1 = p2[1] - p1[1];
243 let y_3_2 = p3[1] - p2[1];
245 let x_2_1 = p2[0] - p1[0];
247 let x_3_2 = p3[0] - p2[0];
249
250 let slope_a = y_2_1 / x_2_1;
252 let slope_b = y_3_2 / x_3_2;
253
254 let y_1_3 = p1[1] - p3[1];
257 let x_1_2 = p1[0] + p2[0];
259 let x_2_3 = p2[0] + p3[0];
261 let y_1_2 = p1[1] + p2[1];
263
264 let numerator = (slope_a * slope_b * y_1_3) + (slope_b * x_1_2) - (slope_a * x_2_3);
266 let x = numerator / (2.0 * (slope_b - slope_a));
267
268 let y = ((-1.0 / slope_a) * (x - (x_1_2 / 2.0))) + (y_1_2 / 2.0);
269
270 [x, y]
271}
272
273pub struct CircleParams {
274 pub center: Point2d,
275 pub radius: f64,
276}
277
278pub fn calculate_circle_from_3_points(points: [Point2d; 3]) -> CircleParams {
279 let center: Point2d = calculate_circle_center(points[0].into(), points[1].into(), points[2].into()).into();
280 CircleParams {
281 center,
282 radius: distance(center, points[1]),
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use pretty_assertions::assert_eq;
290
291 use super::{get_x_component, get_y_component, Angle};
292 use crate::SourceRange;
293
294 static EACH_QUAD: [(i32, [i32; 2]); 12] = [
295 (-315, [1, 1]),
296 (-225, [-1, 1]),
297 (-135, [-1, -1]),
298 (-45, [1, -1]),
299 (45, [1, 1]),
300 (135, [-1, 1]),
301 (225, [-1, -1]),
302 (315, [1, -1]),
303 (405, [1, 1]),
304 (495, [-1, 1]),
305 (585, [-1, -1]),
306 (675, [1, -1]),
307 ];
308
309 #[test]
310 fn test_get_y_component() {
311 let mut expected = Vec::new();
312 let mut results = Vec::new();
313
314 for &(angle, expected_result) in EACH_QUAD.iter() {
315 let res = get_y_component(Angle::from_degrees(angle as f64), 1.0);
316 results.push([res.x.round() as i32, res.y.round() as i32]);
317 expected.push(expected_result);
318 }
319
320 assert_eq!(results, expected);
321
322 let result = get_y_component(Angle::zero(), 1.0);
323 assert_eq!(result.x as i32, 1);
324 assert_eq!(result.y as i32, 0);
325
326 let result = get_y_component(Angle::from_degrees(90.0), 1.0);
327 assert_eq!(result.x as i32, 1);
328 assert!(result.y > 100000.0);
329
330 let result = get_y_component(Angle::from_degrees(180.0), 1.0);
331 assert_eq!(result.x as i32, -1);
332 assert!((result.y - 0.0).abs() < f64::EPSILON);
333
334 let result = get_y_component(Angle::from_degrees(270.0), 1.0);
335 assert_eq!(result.x as i32, -1);
336 assert!(result.y < -100000.0);
337 }
338
339 #[test]
340 fn test_get_x_component() {
341 let mut expected = Vec::new();
342 let mut results = Vec::new();
343
344 for &(angle, expected_result) in EACH_QUAD.iter() {
345 let res = get_x_component(Angle::from_degrees(angle as f64), 1.0);
346 results.push([res.x.round() as i32, res.y.round() as i32]);
347 expected.push(expected_result);
348 }
349
350 assert_eq!(results, expected);
351
352 let result = get_x_component(Angle::zero(), 1.0);
353 assert!(result.x > 100000.0);
354 assert_eq!(result.y as i32, 1);
355
356 let result = get_x_component(Angle::from_degrees(90.0), 1.0);
357 assert!((result.x - 0.0).abs() < f64::EPSILON);
358 assert_eq!(result.y as i32, 1);
359
360 let result = get_x_component(Angle::from_degrees(180.0), 1.0);
361 assert!(result.x < -100000.0);
362 assert_eq!(result.y as i32, 1);
363
364 let result = get_x_component(Angle::from_degrees(270.0), 1.0);
365 assert!((result.x - 0.0).abs() < f64::EPSILON);
366 assert_eq!(result.y as i32, -1);
367 }
368
369 #[test]
370 fn test_arc_center_and_end() {
371 let (center, end) = super::arc_center_and_end(
372 super::Point2d { x: 0.0, y: 0.0 },
373 Angle::zero(),
374 Angle::from_degrees(90.0),
375 1.0,
376 );
377 assert_eq!(center.x.round(), -1.0);
378 assert_eq!(center.y, 0.0);
379 assert_eq!(end.x.round(), -1.0);
380 assert_eq!(end.y, 1.0);
381
382 let (center, end) = super::arc_center_and_end(
383 super::Point2d { x: 0.0, y: 0.0 },
384 Angle::zero(),
385 Angle::from_degrees(180.0),
386 1.0,
387 );
388 assert_eq!(center.x.round(), -1.0);
389 assert_eq!(center.y, 0.0);
390 assert_eq!(end.x.round(), -2.0);
391 assert_eq!(end.y.round(), 0.0);
392
393 let (center, end) = super::arc_center_and_end(
394 super::Point2d { x: 0.0, y: 0.0 },
395 Angle::zero(),
396 Angle::from_degrees(180.0),
397 10.0,
398 );
399 assert_eq!(center.x.round(), -10.0);
400 assert_eq!(center.y, 0.0);
401 assert_eq!(end.x.round(), -20.0);
402 assert_eq!(end.y.round(), 0.0);
403 }
404
405 #[test]
406 fn test_arc_angles() {
407 let (angle_start, angle_end) = super::arc_angles(
408 super::Point2d { x: 0.0, y: 0.0 },
409 super::Point2d { x: -1.0, y: 1.0 },
410 super::Point2d { x: -1.0, y: 0.0 },
411 1.0,
412 SourceRange::default(),
413 )
414 .unwrap();
415 assert_eq!(angle_start.to_degrees().round(), 0.0);
416 assert_eq!(angle_end.to_degrees().round(), 90.0);
417
418 let (angle_start, angle_end) = super::arc_angles(
419 super::Point2d { x: 0.0, y: 0.0 },
420 super::Point2d { x: -2.0, y: 0.0 },
421 super::Point2d { x: -1.0, y: 0.0 },
422 1.0,
423 SourceRange::default(),
424 )
425 .unwrap();
426 assert_eq!(angle_start.to_degrees().round(), 0.0);
427 assert_eq!(angle_end.to_degrees().round(), 180.0);
428
429 let (angle_start, angle_end) = super::arc_angles(
430 super::Point2d { x: 0.0, y: 0.0 },
431 super::Point2d { x: -20.0, y: 0.0 },
432 super::Point2d { x: -10.0, y: 0.0 },
433 10.0,
434 SourceRange::default(),
435 )
436 .unwrap();
437 assert_eq!(angle_start.to_degrees().round(), 0.0);
438 assert_eq!(angle_end.to_degrees().round(), 180.0);
439
440 let result = super::arc_angles(
441 super::Point2d { x: 0.0, y: 5.0 },
442 super::Point2d { x: 5.0, y: 5.0 },
443 super::Point2d { x: 10.0, y: -10.0 },
444 10.0,
445 SourceRange::default(),
446 );
447
448 if let Err(err) = result {
449 assert!(err.to_string().contains("Point Point2d { x: 0.0, y: 5.0 } is not on the circumference of the circle with center Point2d { x: 10.0, y: -10.0 } and radius 10."));
450 } else {
451 panic!("Expected error");
452 }
453 assert_eq!(angle_start.to_degrees().round(), 0.0);
454 assert_eq!(angle_end.to_degrees().round(), 180.0);
455 }
456}
457
458pub type Coords2d = [f64; 2];
459
460pub fn is_points_ccw_wasm(points: &[f64]) -> i32 {
461 let mut sum = 0.0;
464 for i in 0..(points.len() / 2) {
465 let point1 = [points[2 * i], points[2 * i + 1]];
466 let point2 = [points[(2 * i + 2) % points.len()], points[(2 * i + 3) % points.len()]];
467 sum += (point2[0] + point1[0]) * (point2[1] - point1[1]);
468 }
469 sum.signum() as i32
470}
471
472pub fn is_points_ccw(points: &[Coords2d]) -> i32 {
473 let flattened_points: Vec<f64> = points.iter().flat_map(|&p| vec![p[0], p[1]]).collect();
474 is_points_ccw_wasm(&flattened_points)
475}
476
477fn get_slope(start: Coords2d, end: Coords2d) -> (f64, f64) {
478 let slope = if start[0] - end[0] == 0.0 {
479 f64::INFINITY
480 } else {
481 (start[1] - end[1]) / (start[0] - end[0])
482 };
483
484 let perp_slope = if slope == f64::INFINITY { 0.0 } else { -1.0 / slope };
485
486 (slope, perp_slope)
487}
488
489fn get_angle(point1: Coords2d, point2: Coords2d) -> f64 {
490 let delta_x = point2[0] - point1[0];
491 let delta_y = point2[1] - point1[1];
492 let angle = delta_y.atan2(delta_x);
493
494 let result = if angle < 0.0 { angle + 2.0 * PI } else { angle };
495 result * (180.0 / PI)
496}
497
498fn delta_angle(from_angle: f64, to_angle: f64) -> f64 {
499 let norm_from_angle = normalize_rad(from_angle);
500 let norm_to_angle = normalize_rad(to_angle);
501 let provisional = norm_to_angle - norm_from_angle;
502
503 if provisional > -PI && provisional <= PI {
504 provisional
505 } else if provisional > PI {
506 provisional - 2.0 * PI
507 } else if provisional < -PI {
508 provisional + 2.0 * PI
509 } else {
510 provisional
511 }
512}
513
514fn deg2rad(deg: f64) -> f64 {
515 deg * (PI / 180.0)
516}
517
518fn get_mid_point(
519 center: Coords2d,
520 arc_start_point: Coords2d,
521 arc_end_point: Coords2d,
522 tan_previous_point: Coords2d,
523 radius: f64,
524 obtuse: bool,
525) -> Coords2d {
526 let angle_from_center_to_arc_start = get_angle(center, arc_start_point);
527 let angle_from_center_to_arc_end = get_angle(center, arc_end_point);
528 let delta_ang = delta_angle(
529 deg2rad(angle_from_center_to_arc_start),
530 deg2rad(angle_from_center_to_arc_end),
531 );
532 let delta_ang = delta_ang / 2.0 + deg2rad(angle_from_center_to_arc_start);
533 let shortest_arc_mid_point: Coords2d = [
534 delta_ang.cos() * radius + center[0],
535 delta_ang.sin() * radius + center[1],
536 ];
537 let opposite_delta = delta_ang + PI;
538 let longest_arc_mid_point: Coords2d = [
539 opposite_delta.cos() * radius + center[0],
540 opposite_delta.sin() * radius + center[1],
541 ];
542
543 let rotation_direction_original_points = is_points_ccw(&[tan_previous_point, arc_start_point, arc_end_point]);
544 let rotation_direction_points_on_arc = is_points_ccw(&[arc_start_point, shortest_arc_mid_point, arc_end_point]);
545 if rotation_direction_original_points != rotation_direction_points_on_arc && obtuse {
546 longest_arc_mid_point
547 } else {
548 shortest_arc_mid_point
549 }
550}
551
552fn intersect_point_n_slope(point1: Coords2d, slope1: f64, point2: Coords2d, slope2: f64) -> Coords2d {
553 let x = if slope1.abs() == f64::INFINITY {
554 point1[0]
555 } else if slope2.abs() == f64::INFINITY {
556 point2[0]
557 } else {
558 (point2[1] - slope2 * point2[0] - point1[1] + slope1 * point1[0]) / (slope1 - slope2)
559 };
560 let y = if slope1.abs() != f64::INFINITY {
561 slope1 * x - slope1 * point1[0] + point1[1]
562 } else {
563 slope2 * x - slope2 * point2[0] + point2[1]
564 };
565 [x, y]
566}
567
568pub struct TangentialArcInfoInput {
570 pub arc_start_point: Coords2d,
572 pub arc_end_point: Coords2d,
574 pub tan_previous_point: Coords2d,
576 pub obtuse: bool,
578}
579
580#[allow(dead_code)]
582pub struct TangentialArcInfoOutput {
583 pub center: Coords2d,
585 pub arc_mid_point: Coords2d,
587 pub radius: f64,
589 pub start_angle: f64,
591 pub end_angle: f64,
593 pub ccw: i32,
595 pub arc_length: f64,
597}
598
599pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialArcInfoOutput {
603 let (_, perp_slope) = get_slope(input.tan_previous_point, input.arc_start_point);
604 let tangential_line_perp_slope = perp_slope;
605
606 let mid_point: Coords2d = [
608 (input.arc_start_point[0] + input.arc_end_point[0]) / 2.0,
609 (input.arc_start_point[1] + input.arc_end_point[1]) / 2.0,
610 ];
611
612 let slope_mid_point_line = get_slope(input.arc_start_point, mid_point);
613
614 let center: Coords2d;
615 let radius: f64;
616
617 if tangential_line_perp_slope == slope_mid_point_line.0 {
618 center = mid_point;
621 radius =
622 ((input.arc_start_point[0] - center[0]).powi(2) + (input.arc_start_point[1] - center[1]).powi(2)).sqrt();
623 } else {
624 center = intersect_point_n_slope(
625 mid_point,
626 slope_mid_point_line.1,
627 input.arc_start_point,
628 tangential_line_perp_slope,
629 );
630 radius =
631 ((input.arc_start_point[0] - center[0]).powi(2) + (input.arc_start_point[1] - center[1]).powi(2)).sqrt();
632 }
633
634 let arc_mid_point = get_mid_point(
635 center,
636 input.arc_start_point,
637 input.arc_end_point,
638 input.tan_previous_point,
639 radius,
640 input.obtuse,
641 );
642
643 let start_angle = (input.arc_start_point[1] - center[1]).atan2(input.arc_start_point[0] - center[0]);
644 let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]);
645 let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]);
646
647 let arc_mid_angle = (arc_mid_point[1] - center[1]).atan2(arc_mid_point[0] - center[0]);
648 let start_to_mid_arc_length = radius
649 * delta(Angle::from_radians(start_angle), Angle::from_radians(arc_mid_angle))
650 .to_radians()
651 .abs();
652 let mid_to_end_arc_length = radius
653 * delta(Angle::from_radians(arc_mid_angle), Angle::from_radians(end_angle))
654 .to_radians()
655 .abs();
656 let arc_length = start_to_mid_arc_length + mid_to_end_arc_length;
657
658 TangentialArcInfoOutput {
659 center,
660 radius,
661 arc_mid_point,
662 start_angle,
663 end_angle,
664 ccw,
665 arc_length,
666 }
667}
668
669#[cfg(test)]
670mod get_tangential_arc_to_info_tests {
671 use approx::assert_relative_eq;
672
673 use super::*;
674
675 fn round_to_three_decimals(num: f64) -> f64 {
676 (num * 1000.0).round() / 1000.0
677 }
678
679 #[test]
680 fn test_basic_case() {
681 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
682 tan_previous_point: [0.0, -5.0],
683 arc_start_point: [0.0, 0.0],
684 arc_end_point: [4.0, 0.0],
685 obtuse: true,
686 });
687 assert_relative_eq!(result.center[0], 2.0);
688 assert_relative_eq!(result.center[1], 0.0);
689 assert_relative_eq!(result.arc_mid_point[0], 2.0);
690 assert_relative_eq!(result.arc_mid_point[1], 2.0);
691 assert_relative_eq!(result.radius, 2.0);
692 assert_relative_eq!(result.start_angle, PI);
693 assert_relative_eq!(result.end_angle, 0.0);
694 assert_eq!(result.ccw, -1);
695 }
696
697 #[test]
698 fn basic_case_with_arc_centered_at_0_0_and_the_tangential_line_being_45_degrees() {
699 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
700 tan_previous_point: [0.0, -4.0],
701 arc_start_point: [2.0, -2.0],
702 arc_end_point: [-2.0, 2.0],
703 obtuse: true,
704 });
705 assert_relative_eq!(result.center[0], 0.0);
706 assert_relative_eq!(result.center[1], 0.0);
707 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[0]), 2.0);
708 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[1]), 2.0);
709 assert_relative_eq!(result.radius, (2.0f64 * 2.0 + 2.0 * 2.0).sqrt());
710 assert_relative_eq!(result.start_angle, -PI / 4.0);
711 assert_relative_eq!(result.end_angle, 3.0 * PI / 4.0);
712 assert_eq!(result.ccw, 1);
713 }
714
715 #[test]
716 fn test_get_tangential_arc_to_info_moving_arc_end_point() {
717 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
718 tan_previous_point: [0.0, -4.0],
719 arc_start_point: [2.0, -2.0],
720 arc_end_point: [2.0, 2.0],
721 obtuse: true,
722 });
723 let expected_radius = (2.0f64 * 2.0 + 2.0 * 2.0).sqrt();
724 assert_relative_eq!(round_to_three_decimals(result.center[0]), 0.0);
725 assert_relative_eq!(result.center[1], 0.0);
726 assert_relative_eq!(result.arc_mid_point[0], expected_radius);
727 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[1]), -0.0);
728 assert_relative_eq!(result.radius, expected_radius);
729 assert_relative_eq!(result.start_angle, -PI / 4.0);
730 assert_relative_eq!(result.end_angle, PI / 4.0);
731 assert_eq!(result.ccw, 1);
732 }
733
734 #[test]
735 fn test_get_tangential_arc_to_info_moving_arc_end_point_again() {
736 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
737 tan_previous_point: [0.0, -4.0],
738 arc_start_point: [2.0, -2.0],
739 arc_end_point: [-2.0, -2.0],
740 obtuse: true,
741 });
742 let expected_radius = (2.0f64 * 2.0 + 2.0 * 2.0).sqrt();
743 assert_relative_eq!(result.center[0], 0.0);
744 assert_relative_eq!(result.center[1], 0.0);
745 assert_relative_eq!(result.radius, expected_radius);
746 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[0]), 0.0);
747 assert_relative_eq!(result.arc_mid_point[1], expected_radius);
748 assert_relative_eq!(result.start_angle, -PI / 4.0);
749 assert_relative_eq!(result.end_angle, -3.0 * PI / 4.0);
750 assert_eq!(result.ccw, 1);
751 }
752
753 #[test]
754 fn test_get_tangential_arc_to_info_acute_moving_arc_end_point() {
755 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
756 tan_previous_point: [0.0, -4.0],
757 arc_start_point: [2.0, -2.0],
758 arc_end_point: [-2.0, -2.0],
759 obtuse: false,
760 });
761 let expected_radius = (2.0f64 * 2.0 + 2.0 * 2.0).sqrt();
762 assert_relative_eq!(result.center[0], 0.0);
763 assert_relative_eq!(result.center[1], 0.0);
764 assert_relative_eq!(result.radius, expected_radius);
765 assert_relative_eq!(round_to_three_decimals(result.arc_mid_point[0]), -0.0);
766 assert_relative_eq!(result.arc_mid_point[1], -expected_radius);
767 assert_relative_eq!(result.start_angle, -PI / 4.0);
768 assert_relative_eq!(result.end_angle, -3.0 * PI / 4.0);
769 assert_eq!(result.ccw, -1);
771 }
772
773 #[test]
774 fn test_get_tangential_arc_to_info_obtuse_with_wrap_around() {
775 let arc_end = (std::f64::consts::PI / 4.0).cos() * 2.0;
776 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
777 tan_previous_point: [2.0, -4.0],
778 arc_start_point: [2.0, 0.0],
779 arc_end_point: [0.0, -2.0],
780 obtuse: true,
781 });
782 assert_relative_eq!(result.center[0], -0.0);
783 assert_relative_eq!(result.center[1], 0.0);
784 assert_relative_eq!(result.radius, 2.0);
785 assert_relative_eq!(result.arc_mid_point[0], -arc_end);
786 assert_relative_eq!(result.arc_mid_point[1], arc_end);
787 assert_relative_eq!(result.start_angle, 0.0);
788 assert_relative_eq!(result.end_angle, -PI / 2.0);
789 assert_eq!(result.ccw, 1);
790 }
791
792 #[test]
793 fn test_arc_length_obtuse_cw() {
794 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
795 tan_previous_point: [-1.0, -1.0],
796 arc_start_point: [-1.0, 0.0],
797 arc_end_point: [0.0, -1.0],
798 obtuse: true,
799 });
800 let circumference = 2.0 * PI * result.radius;
801 let expected_length = circumference * 3.0 / 4.0; assert_relative_eq!(result.arc_length, expected_length);
803 }
804
805 #[test]
806 fn test_arc_length_acute_cw() {
807 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
808 tan_previous_point: [-1.0, -1.0],
809 arc_start_point: [-1.0, 0.0],
810 arc_end_point: [0.0, 1.0],
811 obtuse: true,
812 });
813 let circumference = 2.0 * PI * result.radius;
814 let expected_length = circumference / 4.0; assert_relative_eq!(result.arc_length, expected_length);
816 }
817
818 #[test]
819 fn test_arc_length_obtuse_ccw() {
820 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
821 tan_previous_point: [1.0, -1.0],
822 arc_start_point: [1.0, 0.0],
823 arc_end_point: [0.0, -1.0],
824 obtuse: true,
825 });
826 let circumference = 2.0 * PI * result.radius;
827 let expected_length = circumference * 3.0 / 4.0; assert_relative_eq!(result.arc_length, expected_length);
829 }
830
831 #[test]
832 fn test_arc_length_acute_ccw() {
833 let result = get_tangential_arc_to_info(TangentialArcInfoInput {
834 tan_previous_point: [1.0, -1.0],
835 arc_start_point: [1.0, 0.0],
836 arc_end_point: [0.0, 1.0],
837 obtuse: true,
838 });
839 let circumference = 2.0 * PI * result.radius;
840 let expected_length = circumference / 4.0; assert_relative_eq!(result.arc_length, expected_length);
842 }
843}
844
845pub fn get_tangent_point_from_previous_arc(
846 last_arc_center: Coords2d,
847 last_arc_ccw: bool,
848 last_arc_end: Coords2d,
849) -> Coords2d {
850 let angle_from_old_center_to_arc_start = get_angle(last_arc_center, last_arc_end);
851 let tangential_angle = angle_from_old_center_to_arc_start + if last_arc_ccw { -90.0 } else { 90.0 };
852 [
854 tangential_angle.to_radians().cos() * 10.0 + last_arc_end[0],
855 tangential_angle.to_radians().sin() * 10.0 + last_arc_end[1],
856 ]
857}