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