scirs2_spatial/collision/
narrowphase.rs

1//! Narrow-phase collision detection algorithms
2//!
3//! This module provides detailed collision detection tests between different geometric objects.
4//! These tests determine precise collision information after broad-phase culling has been applied.
5
6use super::shapes::{
7    Box2D, Box3D, Circle, LineSegment2D, LineSegment3D, Sphere, Triangle2D, Triangle3D,
8};
9
10// ---------------------------------------------------------------------------
11// 2D Point Collision Tests
12// ---------------------------------------------------------------------------
13
14/// Tests if a point is inside a circle
15///
16/// # Arguments
17///
18/// * `point` - A 2D point [x, y]
19/// * `circle` - The circle to test against
20///
21/// # Returns
22///
23/// `true` if the point is inside or on the boundary of the circle, `false` otherwise
24#[allow(dead_code)]
25pub fn point_circle_collision(point: &[f64; 2], circle: &Circle) -> bool {
26    let dx = point[0] - circle.center[0];
27    let dy = point[1] - circle.center[1];
28    let distance_squared = dx * dx + dy * dy;
29
30    distance_squared <= circle.radius * circle.radius
31}
32
33/// Tests if a point is inside a 2D axis-aligned bounding box
34///
35/// # Arguments
36///
37/// * `point` - A 2D point [x, y]
38/// * `box2d` - The 2D box to test against
39///
40/// # Returns
41///
42/// `true` if the point is inside or on the boundary of the box, `false` otherwise
43#[allow(dead_code)]
44pub fn point_box2d_collision(point: &[f64; 2], box2d: &Box2D) -> bool {
45    point[0] >= box2d.min[0]
46        && point[0] <= box2d.max[0]
47        && point[1] >= box2d.min[1]
48        && point[1] <= box2d.max[1]
49}
50
51/// Tests if a point is inside a 2D triangle
52///
53/// # Arguments
54///
55/// * `point` - A 2D point [x, y]
56/// * `triangle` - The 2D triangle to test against
57///
58/// # Returns
59///
60/// `true` if the point is inside or on the boundary of the triangle, `false` otherwise
61#[allow(dead_code)]
62pub fn point_triangle2d_collision(point: &[f64; 2], triangle: &Triangle2D) -> bool {
63    // Compute barycentric coordinates
64    let area = 0.5
65        * ((triangle.v2[0] - triangle.v1[0]) * (triangle.v3[1] - triangle.v1[1])
66            - (triangle.v3[0] - triangle.v1[0]) * (triangle.v2[1] - triangle.v1[1]))
67            .abs();
68
69    if area == 0.0 {
70        return false; // Degenerate triangle
71    }
72
73    let a = ((triangle.v2[0] - point[0]) * (triangle.v3[1] - point[1])
74        - (triangle.v3[0] - point[0]) * (triangle.v2[1] - point[1]))
75        .abs()
76        / (2.0 * area);
77
78    let b = ((triangle.v3[0] - point[0]) * (triangle.v1[1] - point[1])
79        - (triangle.v1[0] - point[0]) * (triangle.v3[1] - point[1]))
80        .abs()
81        / (2.0 * area);
82
83    let c = 1.0 - a - b;
84
85    // Point is inside if all coordinates are within [0, 1]
86    // Using small epsilon for floating-point precision
87    const EPSILON: f64 = 1e-10;
88    a >= -EPSILON
89        && b >= -EPSILON
90        && c >= -EPSILON
91        && a <= 1.0 + EPSILON
92        && b <= 1.0 + EPSILON
93        && c <= 1.0 + EPSILON
94}
95
96// ---------------------------------------------------------------------------
97// 3D Point Collision Tests
98// ---------------------------------------------------------------------------
99
100/// Tests if a point is inside a sphere
101///
102/// # Arguments
103///
104/// * `point` - A 3D point [x, y, z]
105/// * `sphere` - The sphere to test against
106///
107/// # Returns
108///
109/// `true` if the point is inside or on the boundary of the sphere, `false` otherwise
110#[allow(dead_code)]
111pub fn point_sphere_collision(point: &[f64; 3], sphere: &Sphere) -> bool {
112    let dx = point[0] - sphere.center[0];
113    let dy = point[1] - sphere.center[1];
114    let dz = point[2] - sphere.center[2];
115    let distance_squared = dx * dx + dy * dy + dz * dz;
116
117    distance_squared <= sphere.radius * sphere.radius
118}
119
120/// Tests if a point is inside a 3D axis-aligned bounding box
121///
122/// # Arguments
123///
124/// * `point` - A 3D point [x, y, z]
125/// * `box3d` - The 3D box to test against
126///
127/// # Returns
128///
129/// `true` if the point is inside or on the boundary of the box, `false` otherwise
130#[allow(dead_code)]
131pub fn point_box3d_collision(point: &[f64; 3], box3d: &Box3D) -> bool {
132    point[0] >= box3d.min[0]
133        && point[0] <= box3d.max[0]
134        && point[1] >= box3d.min[1]
135        && point[1] <= box3d.max[1]
136        && point[2] >= box3d.min[2]
137        && point[2] <= box3d.max[2]
138}
139
140/// Tests if a point is inside a 3D triangle
141///
142/// # Arguments
143///
144/// * `point` - A 3D point [x, y, z]
145/// * `triangle` - The 3D triangle to test against
146///
147/// # Returns
148///
149/// `true` if the point is on the triangle, `false` otherwise
150#[allow(dead_code)]
151pub fn point_triangle3d_collision(point: &[f64; 3], triangle: &Triangle3D) -> bool {
152    // For a 3D triangle, we need to check if the point is on the plane of the triangle
153    // and then check if it's inside the triangle
154
155    // First, compute the normal vector of the triangle
156    let edge1 = [
157        triangle.v2[0] - triangle.v1[0],
158        triangle.v2[1] - triangle.v1[1],
159        triangle.v2[2] - triangle.v1[2],
160    ];
161
162    let edge2 = [
163        triangle.v3[0] - triangle.v1[0],
164        triangle.v3[1] - triangle.v1[1],
165        triangle.v3[2] - triangle.v1[2],
166    ];
167
168    // Cross product of the two edges gives the normal
169    let normal = [
170        edge1[1] * edge2[2] - edge1[2] * edge2[1],
171        edge1[2] * edge2[0] - edge1[0] * edge2[2],
172        edge1[0] * edge2[1] - edge1[1] * edge2[0],
173    ];
174
175    // Normalize the normal
176    let normal_length =
177        (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
178    if normal_length == 0.0 {
179        return false; // Degenerate triangle
180    }
181
182    let normalized_normal = [
183        normal[0] / normal_length,
184        normal[1] / normal_length,
185        normal[2] / normal_length,
186    ];
187
188    // Distance from point to the plane of the triangle
189    let dist_to_plane = (point[0] - triangle.v1[0]) * normalized_normal[0]
190        + (point[1] - triangle.v1[1]) * normalized_normal[1]
191        + (point[2] - triangle.v1[2]) * normalized_normal[2];
192
193    // Check if the point is close to the plane (within small epsilon)
194    const EPSILON: f64 = 1e-6;
195    if dist_to_plane.abs() > EPSILON {
196        return false; // Point is not on the plane
197    }
198
199    // Project the triangle and point onto a 2D plane for inside/outside test
200    // Choose the dimension with the largest normal component to drop
201    let max_component = if normalized_normal[0].abs() > normalized_normal[1].abs() {
202        if normalized_normal[0].abs() > normalized_normal[2].abs() {
203            0
204        } else {
205            2
206        }
207    } else if normalized_normal[1].abs() > normalized_normal[2].abs() {
208        1
209    } else {
210        2
211    };
212
213    // Extract the coordinates for the 2D projection
214    let mut p1 = [0.0, 0.0];
215    let mut p2 = [0.0, 0.0];
216    let mut p3 = [0.0, 0.0];
217    let mut pp = [0.0, 0.0];
218
219    match max_component {
220        0 => {
221            // Drop the x-coordinate
222            p1[0] = triangle.v1[1];
223            p1[1] = triangle.v1[2];
224            p2[0] = triangle.v2[1];
225            p2[1] = triangle.v2[2];
226            p3[0] = triangle.v3[1];
227            p3[1] = triangle.v3[2];
228            pp[0] = point[1];
229            pp[1] = point[2];
230        }
231        1 => {
232            // Drop the y-coordinate
233            p1[0] = triangle.v1[0];
234            p1[1] = triangle.v1[2];
235            p2[0] = triangle.v2[0];
236            p2[1] = triangle.v2[2];
237            p3[0] = triangle.v3[0];
238            p3[1] = triangle.v3[2];
239            pp[0] = point[0];
240            pp[1] = point[2];
241        }
242        _ => {
243            // Drop the z-coordinate
244            p1[0] = triangle.v1[0];
245            p1[1] = triangle.v1[1];
246            p2[0] = triangle.v2[0];
247            p2[1] = triangle.v2[1];
248            p3[0] = triangle.v3[0];
249            p3[1] = triangle.v3[1];
250            pp[0] = point[0];
251            pp[1] = point[1];
252        }
253    }
254
255    // Create a 2D triangle and use the 2D point-triangle test
256    let triangle2d = Triangle2D {
257        v1: p1,
258        v2: p2,
259        v3: p3,
260    };
261
262    point_triangle2d_collision(&pp, &triangle2d)
263}
264
265// ---------------------------------------------------------------------------
266// 2D Object-Object Collision Tests
267// ---------------------------------------------------------------------------
268
269/// Tests if two circles collide
270///
271/// # Arguments
272///
273/// * `circle1` - The first circle
274/// * `circle2` - The second circle
275///
276/// # Returns
277///
278/// `true` if the circles intersect, `false` otherwise
279#[allow(dead_code)]
280pub fn circle_circle_collision(circle1: &Circle, circle2: &Circle) -> bool {
281    let dx = circle1.center[0] - circle2.center[0];
282    let dy = circle1.center[1] - circle2.center[1];
283    let distance_squared = dx * dx + dy * dy;
284    let sum_of_radii = circle1.radius + circle2.radius;
285
286    distance_squared <= sum_of_radii * sum_of_radii
287}
288
289/// Tests if a circle and a 2D box collide
290///
291/// # Arguments
292///
293/// * `circle` - The circle
294/// * `box2d` - The 2D box
295///
296/// # Returns
297///
298/// `true` if the circle and box intersect, `false` otherwise
299#[allow(dead_code)]
300pub fn circle_box2d_collision(circle: &Circle, box2d: &Box2D) -> bool {
301    // Find the closest point on the box to the _circle center
302    let closest_x = circle.center[0].max(box2d.min[0]).min(box2d.max[0]);
303    let closest_y = circle.center[1].max(box2d.min[1]).min(box2d.max[1]);
304
305    // Calculate distance from the closest point to the _circle center
306    let dx = circle.center[0] - closest_x;
307    let dy = circle.center[1] - closest_y;
308    let distance_squared = dx * dx + dy * dy;
309
310    // Collision occurs if the distance is less than or equal to the radius
311    distance_squared <= circle.radius * circle.radius
312}
313
314/// Tests if two 2D line segments intersect
315///
316/// # Arguments
317///
318/// * `line1` - The first line segment
319/// * `line2` - The second line segment
320///
321/// # Returns
322///
323/// `true` if the line segments intersect, `false` otherwise
324#[allow(dead_code)]
325pub fn line2d_line2d_collision(line1: &LineSegment2D, line2: &LineSegment2D) -> bool {
326    // Calculate the orientation of three points
327    let orientation = |p: &[f64; 2], q: &[f64; 2], r: &[f64; 2]| -> i32 {
328        let val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
329
330        if val.abs() < 1e-10 {
331            0 // Collinear
332        } else if val > 0.0 {
333            1 // Clockwise
334        } else {
335            2 // Counterclockwise
336        }
337    };
338
339    // Check if point q is on segment pr
340    let on_segment = |p: &[f64; 2], q: &[f64; 2], r: &[f64; 2]| -> bool {
341        q[0] <= p[0].max(r[0])
342            && q[0] >= p[0].min(r[0])
343            && q[1] <= p[1].max(r[1])
344            && q[1] >= p[1].min(r[1])
345    };
346
347    let o1 = orientation(&line1.start, &line1.end, &line2.start);
348    let o2 = orientation(&line1.start, &line1.end, &line2.end);
349    let o3 = orientation(&line2.start, &line2.end, &line1.start);
350    let o4 = orientation(&line2.start, &line2.end, &line1.end);
351
352    // General case: Different orientations
353    if o1 != o2 && o3 != o4 {
354        return true;
355    }
356
357    // Special cases: collinear points
358    if o1 == 0 && on_segment(&line1.start, &line2.start, &line1.end) {
359        return true;
360    }
361    if o2 == 0 && on_segment(&line1.start, &line2.end, &line1.end) {
362        return true;
363    }
364    if o3 == 0 && on_segment(&line2.start, &line1.start, &line2.end) {
365        return true;
366    }
367    if o4 == 0 && on_segment(&line2.start, &line1.end, &line2.end) {
368        return true;
369    }
370
371    false
372}
373
374/// Tests if two 2D boxes collide
375///
376/// # Arguments
377///
378/// * `box1` - The first box
379/// * `box2` - The second box
380///
381/// # Returns
382///
383/// `true` if the boxes intersect, `false` otherwise
384#[allow(dead_code)]
385pub fn box2d_box2d_collision(box1: &Box2D, box2: &Box2D) -> bool {
386    // Check if the boxes overlap in both x and y dimensions
387    box1.min[0] <= box2.max[0]
388        && box1.max[0] >= box2.min[0]
389        && box1.min[1] <= box2.max[1]
390        && box1.max[1] >= box2.min[1]
391}
392
393/// Tests if a 2D triangle and a circle collide
394///
395/// # Arguments
396///
397/// * `triangle` - The triangle
398/// * `circle` - The circle
399///
400/// # Returns
401///
402/// `true` if the triangle and circle intersect, `false` otherwise
403#[allow(dead_code)]
404pub fn triangle2d_circle_collision(triangle: &Triangle2D, circle: &Circle) -> bool {
405    // Check if the circle center is inside the _triangle
406    if point_triangle2d_collision(&circle.center, triangle) {
407        return true;
408    }
409
410    // Check if the circle intersects any of the _triangle's edges
411    let edges = [
412        LineSegment2D {
413            start: triangle.v1,
414            end: triangle.v2,
415        },
416        LineSegment2D {
417            start: triangle.v2,
418            end: triangle.v3,
419        },
420        LineSegment2D {
421            start: triangle.v3,
422            end: triangle.v1,
423        },
424    ];
425
426    for edge in &edges {
427        // Calculate vector from start to end of the edge
428        let edge_vec = [edge.end[0] - edge.start[0], edge.end[1] - edge.start[1]];
429
430        // Calculate vector from start of the edge to the circle center
431        let circle_vec = [
432            circle.center[0] - edge.start[0],
433            circle.center[1] - edge.start[1],
434        ];
435
436        // Calculate the length of the edge
437        let edge_length_squared = edge_vec[0] * edge_vec[0] + edge_vec[1] * edge_vec[1];
438
439        // Calculate the projection of circle_vec onto edge_vec
440        let t = (circle_vec[0] * edge_vec[0] + circle_vec[1] * edge_vec[1]) / edge_length_squared;
441
442        // Clamp t to the edge
443        let t_clamped = t.clamp(0.0, 1.0);
444
445        // Calculate the closest point on the edge to the circle center
446        let closest_point = [
447            edge.start[0] + t_clamped * edge_vec[0],
448            edge.start[1] + t_clamped * edge_vec[1],
449        ];
450
451        // Calculate the distance from the closest point to the circle center
452        let dx = circle.center[0] - closest_point[0];
453        let dy = circle.center[1] - closest_point[1];
454        let distance_squared = dx * dx + dy * dy;
455
456        // Check if the distance is less than or equal to the radius
457        if distance_squared <= circle.radius * circle.radius {
458            return true;
459        }
460    }
461
462    false
463}
464
465// ---------------------------------------------------------------------------
466// 3D Object-Object Collision Tests
467// ---------------------------------------------------------------------------
468
469/// Tests if two spheres collide
470///
471/// # Arguments
472///
473/// * `sphere1` - The first sphere
474/// * `sphere2` - The second sphere
475///
476/// # Returns
477///
478/// `true` if the spheres intersect, `false` otherwise
479#[allow(dead_code)]
480pub fn sphere_sphere_collision(sphere1: &Sphere, sphere2: &Sphere) -> bool {
481    let dx = sphere1.center[0] - sphere2.center[0];
482    let dy = sphere1.center[1] - sphere2.center[1];
483    let dz = sphere1.center[2] - sphere2.center[2];
484    let distance_squared = dx * dx + dy * dy + dz * dz;
485    let sum_of_radii = sphere1.radius + sphere2.radius;
486
487    distance_squared <= sum_of_radii * sum_of_radii
488}
489
490/// Tests if a sphere and a 3D box collide
491///
492/// # Arguments
493///
494/// * `sphere` - The sphere
495/// * `box3d` - The 3D box
496///
497/// # Returns
498///
499/// `true` if the sphere and box intersect, `false` otherwise
500#[allow(dead_code)]
501pub fn sphere_box3d_collision(sphere: &Sphere, box3d: &Box3D) -> bool {
502    // Find the closest point on the box to the sphere center
503    let closest_x = sphere.center[0].max(box3d.min[0]).min(box3d.max[0]);
504    let closest_y = sphere.center[1].max(box3d.min[1]).min(box3d.max[1]);
505    let closest_z = sphere.center[2].max(box3d.min[2]).min(box3d.max[2]);
506
507    // Calculate distance from the closest point to the sphere center
508    let dx = sphere.center[0] - closest_x;
509    let dy = sphere.center[1] - closest_y;
510    let dz = sphere.center[2] - closest_z;
511    let distance_squared = dx * dx + dy * dy + dz * dz;
512
513    // Collision occurs if the distance is less than or equal to the radius
514    distance_squared <= sphere.radius * sphere.radius
515}
516
517/// Tests if two 3D boxes collide
518///
519/// # Arguments
520///
521/// * `box1` - The first box
522/// * `box2` - The second box
523///
524/// # Returns
525///
526/// `true` if the boxes intersect, `false` otherwise
527#[allow(dead_code)]
528pub fn box3d_box3d_collision(box1: &Box3D, box2: &Box3D) -> bool {
529    // Check if the boxes overlap in all three dimensions
530    box1.min[0] <= box2.max[0]
531        && box1.max[0] >= box2.min[0]
532        && box1.min[1] <= box2.max[1]
533        && box1.max[1] >= box2.min[1]
534        && box1.min[2] <= box2.max[2]
535        && box1.max[2] >= box2.min[2]
536}
537
538/// Tests if two 3D line segments intersect
539///
540/// # Arguments
541///
542/// * `line1` - The first line segment
543/// * `line2` - The second line segment
544///
545/// # Returns
546///
547/// `true` if the line segments intersect, `false` otherwise
548#[allow(dead_code)]
549pub fn line3d_line3d_collision(line1: &LineSegment3D, line2: &LineSegment3D) -> bool {
550    // 3D line segment intersection is more complex than 2D
551    // We need to find the closest points on both lines and check their distance
552
553    // Direction vectors of the lines
554    let d1 = [
555        line1.end[0] - line1.start[0],
556        line1.end[1] - line1.start[1],
557        line1.end[2] - line1.start[2],
558    ];
559
560    let d2 = [
561        line2.end[0] - line2.start[0],
562        line2.end[1] - line2.start[1],
563        line2.end[2] - line2.start[2],
564    ];
565
566    // Vector connecting start points of both lines
567    let r = [
568        line1.start[0] - line2.start[0],
569        line1.start[1] - line2.start[1],
570        line1.start[2] - line2.start[2],
571    ];
572
573    // Dot products for the solution
574    let a = d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]; // |d1|^2
575    let b = d1[0] * d2[0] + d1[1] * d2[1] + d1[2] * d2[2]; // d1 · d2
576    let c = d2[0] * d2[0] + d2[1] * d2[1] + d2[2] * d2[2]; // |d2|^2
577    let d = d1[0] * r[0] + d1[1] * r[1] + d1[2] * r[2]; // d1 · r
578    let e = d2[0] * r[0] + d2[1] * r[1] + d2[2] * r[2]; // d2 · r
579
580    // Check degenerate cases (zero-length segments)
581    if a < 1e-10 || c < 1e-10 {
582        return false;
583    }
584
585    // Calculate parameters for closest points
586    let denom = a * c - b * b;
587
588    // If lines are parallel, use a different method
589    if denom.abs() < 1e-10 {
590        // For parallel lines, check if they are coplanar and overlapping
591
592        // Find if the lines are coplanar
593        let cross_d1_d2 = [
594            d1[1] * d2[2] - d1[2] * d2[1],
595            d1[2] * d2[0] - d1[0] * d2[2],
596            d1[0] * d2[1] - d1[1] * d2[0],
597        ];
598
599        let dot_r_cross = r[0] * cross_d1_d2[0] + r[1] * cross_d1_d2[1] + r[2] * cross_d1_d2[2];
600
601        // If not coplanar, no intersection
602        if dot_r_cross.abs() > 1e-10 {
603            return false;
604        }
605
606        // For coplanar lines, project onto the direction with largest component
607        let abs_d1 = [d1[0].abs(), d1[1].abs(), d1[2].abs()];
608        let max_component = if abs_d1[0] > abs_d1[1] {
609            if abs_d1[0] > abs_d1[2] {
610                0
611            } else {
612                2
613            }
614        } else if abs_d1[1] > abs_d1[2] {
615            1
616        } else {
617            2
618        };
619
620        // Project the lines onto a single dimension
621        let proj_line1_start = line1.start[max_component];
622        let proj_line1_end = line1.end[max_component];
623        let proj_line2_start = line2.start[max_component];
624        let proj_line2_end = line2.end[max_component];
625
626        // Sort the endpoints
627        let (min1, max1) = if proj_line1_start < proj_line1_end {
628            (proj_line1_start, proj_line1_end)
629        } else {
630            (proj_line1_end, proj_line1_start)
631        };
632
633        let (min2, max2) = if proj_line2_start < proj_line2_end {
634            (proj_line2_start, proj_line2_end)
635        } else {
636            (proj_line2_end, proj_line2_start)
637        };
638
639        // Check if the projected intervals overlap
640        return max1 >= min2 && max2 >= min1;
641    }
642
643    // Compute parameters for closest points
644    let mut s = (b * e - c * d) / denom;
645    let mut t = (a * e - b * d) / denom;
646
647    // Clamp parameters to line segments
648    s = s.clamp(0.0, 1.0);
649    t = t.clamp(0.0, 1.0);
650
651    // Calculate the closest points on both lines
652    let closest1 = [
653        line1.start[0] + s * d1[0],
654        line1.start[1] + s * d1[1],
655        line1.start[2] + s * d1[2],
656    ];
657
658    let closest2 = [
659        line2.start[0] + t * d2[0],
660        line2.start[1] + t * d2[1],
661        line2.start[2] + t * d2[2],
662    ];
663
664    // Calculate distance between the closest points
665    let dx = closest1[0] - closest2[0];
666    let dy = closest1[1] - closest2[1];
667    let dz = closest1[2] - closest2[2];
668    let distance_squared = dx * dx + dy * dy + dz * dz;
669
670    // Consider segments as intersecting if closest points are very close
671    const EPSILON: f64 = 1e-10;
672    distance_squared < EPSILON
673}
674
675/// Tests if a sphere and a 3D triangle collide
676///
677/// # Arguments
678///
679/// * `sphere` - The sphere
680/// * `triangle` - The 3D triangle
681///
682/// # Returns
683///
684/// `true` if the sphere and triangle intersect, `false` otherwise
685#[allow(dead_code)]
686pub fn sphere_triangle3d_collision(sphere: &Sphere, triangle: &Triangle3D) -> bool {
687    // First, find the closest point on the triangle to the sphere center
688
689    // Calculate the normal vector of the triangle
690    let edge1 = [
691        triangle.v2[0] - triangle.v1[0],
692        triangle.v2[1] - triangle.v1[1],
693        triangle.v2[2] - triangle.v1[2],
694    ];
695
696    let edge2 = [
697        triangle.v3[0] - triangle.v1[0],
698        triangle.v3[1] - triangle.v1[1],
699        triangle.v3[2] - triangle.v1[2],
700    ];
701
702    // Cross product of the two edges gives the normal
703    let normal = [
704        edge1[1] * edge2[2] - edge1[2] * edge2[1],
705        edge1[2] * edge2[0] - edge1[0] * edge2[2],
706        edge1[0] * edge2[1] - edge1[1] * edge2[0],
707    ];
708
709    // Normalize the normal
710    let normal_length =
711        (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
712    if normal_length < 1e-10 {
713        return false; // Degenerate triangle
714    }
715
716    let normalized_normal = [
717        normal[0] / normal_length,
718        normal[1] / normal_length,
719        normal[2] / normal_length,
720    ];
721
722    // Calculate distance from sphere center to the plane of the triangle
723    let dist_to_plane = (sphere.center[0] - triangle.v1[0]) * normalized_normal[0]
724        + (sphere.center[1] - triangle.v1[1]) * normalized_normal[1]
725        + (sphere.center[2] - triangle.v1[2]) * normalized_normal[2];
726
727    // If the sphere is too far from the plane, no collision
728    if dist_to_plane.abs() > sphere.radius {
729        return false;
730    }
731
732    // Project sphere center onto the triangle's plane
733    let projected_center = [
734        sphere.center[0] - dist_to_plane * normalized_normal[0],
735        sphere.center[1] - dist_to_plane * normalized_normal[1],
736        sphere.center[2] - dist_to_plane * normalized_normal[2],
737    ];
738
739    // Check if the projected center is inside the triangle
740    let inside_triangle = point_triangle3d_collision(&projected_center, triangle);
741
742    if inside_triangle {
743        // If the projected center is inside, we know the sphere intersects the triangle
744        return true;
745    }
746
747    // If not inside, check the edges of the triangle
748    let edges = [
749        LineSegment3D {
750            start: triangle.v1,
751            end: triangle.v2,
752        },
753        LineSegment3D {
754            start: triangle.v2,
755            end: triangle.v3,
756        },
757        LineSegment3D {
758            start: triangle.v3,
759            end: triangle.v1,
760        },
761    ];
762
763    for edge in &edges {
764        // Calculate vector from start to end of the edge
765        let edge_vec = [
766            edge.end[0] - edge.start[0],
767            edge.end[1] - edge.start[1],
768            edge.end[2] - edge.start[2],
769        ];
770
771        // Calculate vector from start of the edge to the sphere center
772        let sphere_vec = [
773            sphere.center[0] - edge.start[0],
774            sphere.center[1] - edge.start[1],
775            sphere.center[2] - edge.start[2],
776        ];
777
778        // Calculate the length of the edge
779        let edge_length_squared =
780            edge_vec[0] * edge_vec[0] + edge_vec[1] * edge_vec[1] + edge_vec[2] * edge_vec[2];
781
782        // Calculate the projection of sphere_vec onto edge_vec
783        let t = (sphere_vec[0] * edge_vec[0]
784            + sphere_vec[1] * edge_vec[1]
785            + sphere_vec[2] * edge_vec[2])
786            / edge_length_squared;
787
788        // Clamp t to the edge
789        let t_clamped = t.clamp(0.0, 1.0);
790
791        // Calculate the closest point on the edge to the sphere center
792        let closest_point = [
793            edge.start[0] + t_clamped * edge_vec[0],
794            edge.start[1] + t_clamped * edge_vec[1],
795            edge.start[2] + t_clamped * edge_vec[2],
796        ];
797
798        // Calculate the distance from the closest point to the sphere center
799        let dx = sphere.center[0] - closest_point[0];
800        let dy = sphere.center[1] - closest_point[1];
801        let dz = sphere.center[2] - closest_point[2];
802        let distance_squared = dx * dx + dy * dy + dz * dz;
803
804        // Check if the distance is less than or equal to the radius
805        if distance_squared <= sphere.radius * sphere.radius {
806            return true;
807        }
808    }
809
810    // Also check the vertices
811    let vertices = [triangle.v1, triangle.v2, triangle.v3];
812
813    for &vertex in &vertices {
814        let dx = sphere.center[0] - vertex[0];
815        let dy = sphere.center[1] - vertex[1];
816        let dz = sphere.center[2] - vertex[2];
817        let distance_squared = dx * dx + dy * dy + dz * dz;
818
819        if distance_squared <= sphere.radius * sphere.radius {
820            return true;
821        }
822    }
823
824    false
825}
826
827// ---------------------------------------------------------------------------
828// Ray Intersection Tests
829// ---------------------------------------------------------------------------
830
831/// Tests if a ray intersects a sphere and returns the distance to intersection and hit point
832///
833/// # Arguments
834///
835/// * `ray_origin` - The origin of the ray [x, y, z]
836/// * `ray_direction` - The direction of the ray [dx, dy, dz] (must be normalized)
837/// * `sphere` - The sphere to test against
838///
839/// # Returns
840///
841/// `Some((distance, hit_point))` if the ray intersects the sphere, `None` otherwise
842#[allow(dead_code)]
843pub fn ray_sphere_collision(
844    ray_origin: &[f64; 3],
845    ray_direction: &[f64; 3],
846    sphere: &Sphere,
847) -> Option<(f64, [f64; 3])> {
848    // Vector from ray _origin to sphere center
849    let oc = [
850        ray_origin[0] - sphere.center[0],
851        ray_origin[1] - sphere.center[1],
852        ray_origin[2] - sphere.center[2],
853    ];
854
855    // Quadratic equation coefficients a*t^2 + b*t + c = 0
856    // where t is the distance along the ray
857
858    // a = dot(ray_direction, ray_direction) which should be 1.0 if ray_direction is normalized
859    let a = ray_direction[0] * ray_direction[0]
860        + ray_direction[1] * ray_direction[1]
861        + ray_direction[2] * ray_direction[2];
862
863    // b = 2 * dot(ray_direction, oc)
864    let b = 2.0 * (ray_direction[0] * oc[0] + ray_direction[1] * oc[1] + ray_direction[2] * oc[2]);
865
866    // c = dot(oc, oc) - sphere.radius^2
867    let c = oc[0] * oc[0] + oc[1] * oc[1] + oc[2] * oc[2] - sphere.radius * sphere.radius;
868
869    // Discriminant determines if the ray intersects the sphere
870    let discriminant = b * b - 4.0 * a * c;
871
872    if discriminant < 0.0 {
873        // No intersection
874        None
875    } else {
876        // Calculate the distance to the intersection point
877        let t = (-b - discriminant.sqrt()) / (2.0 * a);
878
879        // If t is negative, the intersection is behind the ray _origin
880        if t < 0.0 {
881            // Check the other intersection point
882            let t2 = (-b + discriminant.sqrt()) / (2.0 * a);
883            if t2 < 0.0 {
884                None
885            } else {
886                // Calculate hit point
887                let hit_point = [
888                    ray_origin[0] + t2 * ray_direction[0],
889                    ray_origin[1] + t2 * ray_direction[1],
890                    ray_origin[2] + t2 * ray_direction[2],
891                ];
892                Some((t2, hit_point))
893            }
894        } else {
895            // Calculate hit point
896            let hit_point = [
897                ray_origin[0] + t * ray_direction[0],
898                ray_origin[1] + t * ray_direction[1],
899                ray_origin[2] + t * ray_direction[2],
900            ];
901            Some((t, hit_point))
902        }
903    }
904}
905
906/// Tests if a ray intersects a 3D box and returns the distance to intersection, enter/exit times, and hit point
907///
908/// # Arguments
909///
910/// * `ray_origin` - The origin of the ray [x, y, z]
911/// * `ray_direction` - The direction of the ray [dx, dy, dz] (must be normalized)
912/// * `box3d` - The 3D box to test against
913///
914/// # Returns
915///
916/// `Some((t_enter, t_exit, hit_point))` if the ray intersects the box, `None` otherwise
917#[allow(dead_code)]
918pub fn ray_box3d_collision(
919    ray_origin: &[f64; 3],
920    ray_direction: &[f64; 3],
921    box3d: &Box3D,
922) -> Option<(f64, f64, [f64; 3])> {
923    // Efficient slab method for ray-box intersection
924    let mut tmin = (box3d.min[0] - ray_origin[0]) / ray_direction[0];
925    let mut tmax = (box3d.max[0] - ray_origin[0]) / ray_direction[0];
926
927    if tmin > tmax {
928        std::mem::swap(&mut tmin, &mut tmax);
929    }
930
931    let mut tymin = (box3d.min[1] - ray_origin[1]) / ray_direction[1];
932    let mut tymax = (box3d.max[1] - ray_origin[1]) / ray_direction[1];
933
934    if tymin > tymax {
935        std::mem::swap(&mut tymin, &mut tymax);
936    }
937
938    if tmin > tymax || tymin > tmax {
939        return None;
940    }
941
942    if tymin > tmin {
943        tmin = tymin;
944    }
945
946    if tymax < tmax {
947        tmax = tymax;
948    }
949
950    let mut tzmin = (box3d.min[2] - ray_origin[2]) / ray_direction[2];
951    let mut tzmax = (box3d.max[2] - ray_origin[2]) / ray_direction[2];
952
953    if tzmin > tzmax {
954        std::mem::swap(&mut tzmin, &mut tzmax);
955    }
956
957    if tmin > tzmax || tzmin > tmax {
958        return None;
959    }
960
961    if tzmin > tmin {
962        tmin = tzmin;
963    }
964
965    if tzmax < tmax {
966        tmax = tzmax;
967    }
968
969    // If tmax is negative, the ray is intersecting behind the _origin
970    if tmax < 0.0 {
971        return None;
972    }
973
974    // If tmin is negative, the ray starts inside the box
975    let t = if tmin < 0.0 { tmax } else { tmin };
976
977    // Calculate hit point
978    let hit_point = [
979        ray_origin[0] + t * ray_direction[0],
980        ray_origin[1] + t * ray_direction[1],
981        ray_origin[2] + t * ray_direction[2],
982    ];
983
984    Some((tmin, tmax, hit_point))
985}
986
987/// Tests if a ray intersects a 3D triangle and returns the distance to intersection, hit point and barycentric coordinates
988///
989/// # Arguments
990///
991/// * `ray_origin` - The origin of the ray [x, y, z]
992/// * `ray_direction` - The direction of the ray [dx, dy, dz] (must be normalized)
993/// * `triangle` - The 3D triangle to test against
994///
995/// # Returns
996///
997/// `Some((distance, hit_point, barycentric))` if the ray intersects the triangle, `None` otherwise
998#[allow(dead_code)]
999pub fn ray_triangle3d_collision(
1000    ray_origin: &[f64; 3],
1001    ray_direction: &[f64; 3],
1002    triangle: &Triangle3D,
1003) -> Option<(f64, [f64; 3], [f64; 3])> {
1004    // M�llerTrumbore intersection algorithm for ray-triangle intersection
1005
1006    // Edge vectors
1007    let edge1 = [
1008        triangle.v2[0] - triangle.v1[0],
1009        triangle.v2[1] - triangle.v1[1],
1010        triangle.v2[2] - triangle.v1[2],
1011    ];
1012
1013    let edge2 = [
1014        triangle.v3[0] - triangle.v1[0],
1015        triangle.v3[1] - triangle.v1[1],
1016        triangle.v3[2] - triangle.v1[2],
1017    ];
1018
1019    // Calculate h = cross(ray_direction, edge2)
1020    let h = [
1021        ray_direction[1] * edge2[2] - ray_direction[2] * edge2[1],
1022        ray_direction[2] * edge2[0] - ray_direction[0] * edge2[2],
1023        ray_direction[0] * edge2[1] - ray_direction[1] * edge2[0],
1024    ];
1025
1026    // Calculate a = dot(edge1, h)
1027    let a = edge1[0] * h[0] + edge1[1] * h[1] + edge1[2] * h[2];
1028
1029    // If a is very close to 0, the ray is parallel to the triangle
1030    if a.abs() < 1e-10 {
1031        return None;
1032    }
1033
1034    // Calculate f = 1/a
1035    let f = 1.0 / a;
1036
1037    // Calculate s = ray_origin - v1
1038    let s = [
1039        ray_origin[0] - triangle.v1[0],
1040        ray_origin[1] - triangle.v1[1],
1041        ray_origin[2] - triangle.v1[2],
1042    ];
1043
1044    // Calculate u = f * dot(s, h)
1045    let u = f * (s[0] * h[0] + s[1] * h[1] + s[2] * h[2]);
1046
1047    // If u is outside of [0, 1], the ray doesn't intersect the triangle
1048    if !(0.0..=1.0).contains(&u) {
1049        return None;
1050    }
1051
1052    // Calculate q = cross(s, edge1)
1053    let q = [
1054        s[1] * edge1[2] - s[2] * edge1[1],
1055        s[2] * edge1[0] - s[0] * edge1[2],
1056        s[0] * edge1[1] - s[1] * edge1[0],
1057    ];
1058
1059    // Calculate v = f * dot(ray_direction, q)
1060    let v = f * (ray_direction[0] * q[0] + ray_direction[1] * q[1] + ray_direction[2] * q[2]);
1061
1062    // If v is outside of [0, 1] or u + v > 1, the ray doesn't intersect the triangle
1063    if v < 0.0 || u + v > 1.0 {
1064        return None;
1065    }
1066
1067    // Calculate t = f * dot(edge2, q)
1068    let t = f * (edge2[0] * q[0] + edge2[1] * q[1] + edge2[2] * q[2]);
1069
1070    // If t is negative, the intersection is behind the ray _origin
1071    if t < 0.0 {
1072        return None;
1073    }
1074
1075    // Calculate hit point
1076    let hit_point = [
1077        ray_origin[0] + t * ray_direction[0],
1078        ray_origin[1] + t * ray_direction[1],
1079        ray_origin[2] + t * ray_direction[2],
1080    ];
1081
1082    // Barycentric coordinates
1083    let barycentric = [u, v, 1.0 - u - v];
1084
1085    Some((t, hit_point, barycentric))
1086}
1087
1088// ---------------------------------------------------------------------------
1089// GJK (Gilbert-Johnson-Keerthi) Algorithm for Convex Shape Collision Detection
1090// ---------------------------------------------------------------------------
1091
1092/// Trait for shapes that can be used with the GJK algorithm
1093///
1094/// The support function returns the farthest point in the given direction
1095pub trait GJKShape {
1096    /// Returns the farthest point in the shape in the given direction
1097    fn support(&self, direction: &[f64; 3]) -> [f64; 3];
1098}
1099
1100/// Implementation of GJK support function for Sphere
1101impl GJKShape for Sphere {
1102    fn support(&self, direction: &[f64; 3]) -> [f64; 3] {
1103        // Normalize the direction vector
1104        let length = (direction[0] * direction[0]
1105            + direction[1] * direction[1]
1106            + direction[2] * direction[2])
1107            .sqrt();
1108        if length < 1e-10 {
1109            return self.center;
1110        }
1111
1112        let normalized = [
1113            direction[0] / length,
1114            direction[1] / length,
1115            direction[2] / length,
1116        ];
1117
1118        [
1119            self.center[0] + self.radius * normalized[0],
1120            self.center[1] + self.radius * normalized[1],
1121            self.center[2] + self.radius * normalized[2],
1122        ]
1123    }
1124}
1125
1126/// Implementation of GJK support function for Box3D
1127impl GJKShape for Box3D {
1128    fn support(&self, direction: &[f64; 3]) -> [f64; 3] {
1129        [
1130            if direction[0] >= 0.0 {
1131                self.max[0]
1132            } else {
1133                self.min[0]
1134            },
1135            if direction[1] >= 0.0 {
1136                self.max[1]
1137            } else {
1138                self.min[1]
1139            },
1140            if direction[2] >= 0.0 {
1141                self.max[2]
1142            } else {
1143                self.min[2]
1144            },
1145        ]
1146    }
1147}
1148
1149/// Represents a simplex used in the GJK algorithm
1150#[derive(Debug, Clone)]
1151struct GJKSimplex {
1152    points: Vec<[f64; 3]>,
1153}
1154
1155impl GJKSimplex {
1156    fn new() -> Self {
1157        Self {
1158            points: Vec::with_capacity(4),
1159        }
1160    }
1161
1162    fn add_point(&mut self, point: [f64; 3]) {
1163        self.points.push(point);
1164    }
1165
1166    fn size(&self) -> usize {
1167        self.points.len()
1168    }
1169
1170    fn get_point(&self, index: usize) -> Option<[f64; 3]> {
1171        self.points.get(index).copied()
1172    }
1173
1174    #[allow(dead_code)]
1175    fn clear(&mut self) {
1176        self.points.clear();
1177    }
1178
1179    fn set_points(&mut self, points: Vec<[f64; 3]>) {
1180        self.points = points;
1181    }
1182}
1183
1184/// Compute the Minkowski difference support point
1185#[allow(dead_code)]
1186fn support_minkowski_difference<T1: GJKShape, T2: GJKShape>(
1187    shape1: &T1,
1188    shape2: &T2,
1189    direction: &[f64; 3],
1190) -> [f64; 3] {
1191    let support1 = shape1.support(direction);
1192    let neg_direction = [-direction[0], -direction[1], -direction[2]];
1193    let support2 = shape2.support(&neg_direction);
1194
1195    [
1196        support1[0] - support2[0],
1197        support1[1] - support2[1],
1198        support1[2] - support2[2],
1199    ]
1200}
1201
1202/// Vector operations helper functions
1203#[allow(dead_code)]
1204fn dot_product(a: &[f64; 3], b: &[f64; 3]) -> f64 {
1205    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
1206}
1207
1208#[allow(dead_code)]
1209fn cross_product(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
1210    [
1211        a[1] * b[2] - a[2] * b[1],
1212        a[2] * b[0] - a[0] * b[2],
1213        a[0] * b[1] - a[1] * b[0],
1214    ]
1215}
1216
1217#[allow(dead_code)]
1218fn subtract_vectors(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
1219    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
1220}
1221
1222#[allow(dead_code)]
1223fn negate_vector(a: &[f64; 3]) -> [f64; 3] {
1224    [-a[0], -a[1], -a[2]]
1225}
1226
1227/// Handle line simplex (2 points)
1228#[allow(dead_code)]
1229fn handle_line_simplex(simplex: &mut GJKSimplex, direction: &mut [f64; 3]) -> bool {
1230    let a = simplex.get_point(1).unwrap(); // Latest point
1231    let b = simplex.get_point(0).unwrap(); // Previous point
1232
1233    let ab = subtract_vectors(&b, &a);
1234    let ao = negate_vector(&a);
1235
1236    if dot_product(&ab, &ao) > 0.0 {
1237        // Origin is beyond point B in direction AB
1238        *direction = cross_product(&cross_product(&ab, &ao), &ab);
1239    } else {
1240        // Origin is beyond point A
1241        simplex.set_points(vec![a]);
1242        *direction = ao;
1243    }
1244
1245    false // Origin not contained
1246}
1247
1248/// Handle triangle simplex (3 points)
1249#[allow(dead_code)]
1250fn handle_triangle_simplex(simplex: &mut GJKSimplex, direction: &mut [f64; 3]) -> bool {
1251    let a = simplex.get_point(2).unwrap(); // Latest point
1252    let b = simplex.get_point(1).unwrap();
1253    let c = simplex.get_point(0).unwrap();
1254
1255    let ab = subtract_vectors(&b, &a);
1256    let ac = subtract_vectors(&c, &a);
1257    let ao = negate_vector(&a);
1258
1259    let abc = cross_product(&ab, &ac);
1260
1261    if dot_product(&cross_product(&abc, &ac), &ao) > 0.0 {
1262        if dot_product(&ac, &ao) > 0.0 {
1263            // Region AC
1264            simplex.set_points(vec![c, a]);
1265            *direction = cross_product(&cross_product(&ac, &ao), &ac);
1266        } else {
1267            return handle_line_simplex_from_points(simplex, direction, a, b);
1268        }
1269    } else if dot_product(&cross_product(&ab, &abc), &ao) > 0.0 {
1270        return handle_line_simplex_from_points(simplex, direction, a, b);
1271    } else if dot_product(&abc, &ao) > 0.0 {
1272        // Above triangle
1273        *direction = abc;
1274    } else {
1275        // Below triangle
1276        simplex.set_points(vec![b, c, a]);
1277        *direction = negate_vector(&abc);
1278    }
1279
1280    false // Origin not contained
1281}
1282
1283/// Helper function for handling line simplex from specific points
1284#[allow(dead_code)]
1285fn handle_line_simplex_from_points(
1286    simplex: &mut GJKSimplex,
1287    direction: &mut [f64; 3],
1288    a: [f64; 3],
1289    b: [f64; 3],
1290) -> bool {
1291    let ab = subtract_vectors(&b, &a);
1292    let ao = negate_vector(&a);
1293
1294    if dot_product(&ab, &ao) > 0.0 {
1295        simplex.set_points(vec![b, a]);
1296        *direction = cross_product(&cross_product(&ab, &ao), &ab);
1297    } else {
1298        simplex.set_points(vec![a]);
1299        *direction = ao;
1300    }
1301
1302    false
1303}
1304
1305/// Handle tetrahedron simplex (4 points)
1306#[allow(dead_code)]
1307fn handle_tetrahedron_simplex(simplex: &mut GJKSimplex, direction: &mut [f64; 3]) -> bool {
1308    let a = simplex.get_point(3).unwrap(); // Latest point
1309    let b = simplex.get_point(2).unwrap();
1310    let c = simplex.get_point(1).unwrap();
1311    let d = simplex.get_point(0).unwrap();
1312
1313    let ab = subtract_vectors(&b, &a);
1314    let ac = subtract_vectors(&c, &a);
1315    let ad = subtract_vectors(&d, &a);
1316    let ao = negate_vector(&a);
1317
1318    let abc = cross_product(&ab, &ac);
1319    let acd = cross_product(&ac, &ad);
1320    let adb = cross_product(&ad, &ab);
1321
1322    // Check which face the origin is on the outside of
1323    if dot_product(&abc, &ao) > 0.0 {
1324        simplex.set_points(vec![c, b, a]);
1325        return handle_triangle_simplex(simplex, direction);
1326    }
1327
1328    if dot_product(&acd, &ao) > 0.0 {
1329        simplex.set_points(vec![d, c, a]);
1330        return handle_triangle_simplex(simplex, direction);
1331    }
1332
1333    if dot_product(&adb, &ao) > 0.0 {
1334        simplex.set_points(vec![b, d, a]);
1335        return handle_triangle_simplex(simplex, direction);
1336    }
1337
1338    // Origin is inside the tetrahedron
1339    true
1340}
1341
1342/// Handle simplex evolution based on current size
1343#[allow(dead_code)]
1344fn handle_simplex(simplex: &mut GJKSimplex, direction: &mut [f64; 3]) -> bool {
1345    match simplex.size() {
1346        2 => handle_line_simplex(simplex, direction),
1347        3 => handle_triangle_simplex(simplex, direction),
1348        4 => handle_tetrahedron_simplex(simplex, direction),
1349        _ => false,
1350    }
1351}
1352
1353/// GJK collision detection algorithm for two convex shapes
1354///
1355/// # Arguments
1356///
1357/// * `shape1` - First convex shape implementing GJKShape trait
1358/// * `shape2` - Second convex shape implementing GJKShape trait
1359/// * `max_iterations` - Maximum number of iterations (typically 64 is sufficient)
1360///
1361/// # Returns
1362///
1363/// `true` if the shapes are colliding, `false` otherwise
1364///
1365/// # Examples
1366///
1367/// ```
1368/// use scirs2_spatial::collision::{gjk_collision_detection, Sphere, Box3D};
1369///
1370/// let sphere = Sphere {
1371///     center: [0.0, 0.0, 0.0],
1372///     radius: 1.0,
1373/// };
1374///
1375/// let bbox = Box3D {
1376///     min: [-0.5, -0.5, -0.5],
1377///     max: [0.5, 0.5, 0.5],
1378/// };
1379///
1380/// let colliding = gjk_collision_detection(&sphere, &bbox, 64);
1381/// println!("Are the shapes colliding? {}", colliding);
1382/// ```
1383#[allow(dead_code)]
1384pub fn gjk_collision_detection<T1: GJKShape, T2: GJKShape>(
1385    shape1: &T1,
1386    shape2: &T2,
1387    max_iterations: usize,
1388) -> bool {
1389    // Choose initial search direction (can be arbitrary non-zero vector)
1390    let mut direction = [1.0, 0.0, 0.0];
1391
1392    // Get initial support point
1393    let initial_support = support_minkowski_difference(shape1, shape2, &direction);
1394
1395    // If the first support point isn't past the origin, there's no collision
1396    if dot_product(&initial_support, &direction) <= 0.0 {
1397        return false;
1398    }
1399
1400    let mut simplex = GJKSimplex::new();
1401    simplex.add_point(initial_support);
1402
1403    // Flip direction towards origin
1404    direction = negate_vector(&initial_support);
1405
1406    for _ in 0..max_iterations {
1407        let support_point = support_minkowski_difference(shape1, shape2, &direction);
1408
1409        // If we don't make progress past the origin, there's no collision
1410        if dot_product(&support_point, &direction) <= 0.0 {
1411            return false;
1412        }
1413
1414        simplex.add_point(support_point);
1415
1416        // Check if the simplex contains the origin
1417        if handle_simplex(&mut simplex, &mut direction) {
1418            return true; // Collision detected
1419        }
1420    }
1421
1422    false // No collision detected within max _iterations
1423}
1424
1425/// Convenience function for GJK collision detection between two spheres
1426///
1427/// # Arguments
1428///
1429/// * `sphere1` - First sphere
1430/// * `sphere2` - Second sphere
1431///
1432/// # Returns
1433///
1434/// `true` if the spheres are colliding, `false` otherwise
1435#[allow(dead_code)]
1436pub fn gjk_sphere_sphere_collision(sphere1: &Sphere, sphere2: &Sphere) -> bool {
1437    gjk_collision_detection(sphere1, sphere2, 64)
1438}
1439
1440/// Convenience function for GJK collision detection between two boxes
1441///
1442/// # Arguments
1443///
1444/// * `box1` - First box
1445/// * `box2` - Second box
1446///
1447/// # Returns
1448///
1449/// `true` if the boxes are colliding, `false` otherwise
1450#[allow(dead_code)]
1451pub fn gjk_box_box_collision(box1: &Box3D, box2: &Box3D) -> bool {
1452    gjk_collision_detection(box1, box2, 64)
1453}
1454
1455/// Convenience function for GJK collision detection between a sphere and a box
1456///
1457/// # Arguments
1458///
1459/// * `sphere` - The sphere
1460/// * `bbox` - The box
1461///
1462/// # Returns
1463///
1464/// `true` if the sphere and box are colliding, `false` otherwise
1465#[allow(dead_code)]
1466pub fn gjk_sphere_box_collision(sphere: &Sphere, bbox: &Box3D) -> bool {
1467    gjk_collision_detection(sphere, bbox, 64)
1468}