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