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