scirs2_spatial/collision/
continuous.rs

1//! Continuous collision detection algorithms
2//!
3//! This module provides algorithms for detecting collisions between moving objects
4//! over a time interval, rather than just testing for overlap at a specific moment.
5
6use super::shapes::{Box3D, Circle, Sphere, Triangle3D};
7
8/// Tests if two moving spheres will collide within a time step
9///
10/// # Arguments
11///
12/// * `sphere1` - The first sphere
13/// * `velocity1` - The velocity of the first sphere [vx, vy, vz]
14/// * `sphere2` - The second sphere
15/// * `velocity2` - The velocity of the second sphere [vx, vy, vz]
16/// * `time_step` - The time step to check for collision
17///
18/// # Returns
19///
20/// `Some((time, pos1, pos2))` if the spheres will collide within the time step, where pos1 and pos2 are
21/// the positions of the spheres at the time of collision, `None` otherwise
22#[allow(dead_code)]
23pub fn continuous_sphere_sphere_collision(
24    sphere1: &Sphere,
25    velocity1: &[f64; 3],
26    sphere2: &Sphere,
27    velocity2: &[f64; 3],
28    time_step: f64,
29) -> Option<(f64, [f64; 3], [f64; 3])> {
30    // Calculate relative position and velocity
31    let relative_position = [
32        sphere1.center[0] - sphere2.center[0],
33        sphere1.center[1] - sphere2.center[1],
34        sphere1.center[2] - sphere2.center[2],
35    ];
36
37    let relative_velocity = [
38        velocity1[0] - velocity2[0],
39        velocity1[1] - velocity2[1],
40        velocity1[2] - velocity2[2],
41    ];
42
43    // Calculate the quadratic equation coefficients
44    // a*t^2 + b*t + c = 0, where t is the time of collision
45
46    // a = |relative_velocity|^2
47    let a = relative_velocity[0] * relative_velocity[0]
48        + relative_velocity[1] * relative_velocity[1]
49        + relative_velocity[2] * relative_velocity[2];
50
51    // If a is very close to 0, the spheres are moving at the same velocity
52    if a < 1e-10 {
53        // Check if they are already colliding
54        let distance_squared = relative_position[0] * relative_position[0]
55            + relative_position[1] * relative_position[1]
56            + relative_position[2] * relative_position[2];
57        let sum_of_radii = sphere1.radius + sphere2.radius;
58
59        if distance_squared <= sum_of_radii * sum_of_radii {
60            return Some((0.0, sphere1.center, sphere2.center));
61        } else {
62            return None;
63        }
64    }
65
66    // b = 2 * dot(relative_velocity, relative_position)
67    let b = 2.0
68        * (relative_velocity[0] * relative_position[0]
69            + relative_velocity[1] * relative_position[1]
70            + relative_velocity[2] * relative_position[2]);
71
72    // c = |relative_position|^2 - (sphere1.radius + sphere2.radius)^2
73    let c = relative_position[0] * relative_position[0]
74        + relative_position[1] * relative_position[1]
75        + relative_position[2] * relative_position[2]
76        - (sphere1.radius + sphere2.radius) * (sphere1.radius + sphere2.radius);
77
78    // If c <= 0, the spheres are already colliding
79    if c <= 0.0 {
80        return Some((0.0, sphere1.center, sphere2.center));
81    }
82
83    // Discriminant determines if the spheres will collide
84    let discriminant = b * b - 4.0 * a * c;
85
86    if discriminant < 0.0 {
87        // No collision will occur
88        return None;
89    }
90
91    // Calculate the time of collision
92    let t = (-b - discriminant.sqrt()) / (2.0 * a);
93
94    // Check if the collision occurs within the time _step
95    if t >= 0.0 && t <= time_step {
96        Some((
97            t,
98            [
99                sphere1.center[0] + velocity1[0] * t,
100                sphere1.center[1] + velocity1[1] * t,
101                sphere1.center[2] + velocity1[2] * t,
102            ],
103            [
104                sphere2.center[0] + velocity2[0] * t,
105                sphere2.center[1] + velocity2[1] * t,
106                sphere2.center[2] + velocity2[2] * t,
107            ],
108        ))
109    } else {
110        None
111    }
112}
113
114/// Tests if a moving circle will collide with a static circle within a time step
115///
116/// # Arguments
117///
118/// * `circle1` - The moving circle
119/// * `velocity1` - The velocity of the moving circle [vx, vy]
120/// * `circle2` - The static circle
121/// * `time_step` - The time step to check for collision
122///
123/// # Returns
124///
125/// `Some(time)` if the circles will collide within the time step, `None` otherwise
126#[allow(dead_code)]
127pub fn continuous_circle_circle_collision(
128    circle1: &Circle,
129    velocity1: &[f64; 2],
130    circle2: &Circle,
131    time_step: f64,
132) -> Option<f64> {
133    // Similar to the 3D case, but in 2D
134    let relative_position = [
135        circle1.center[0] - circle2.center[0],
136        circle1.center[1] - circle2.center[1],
137    ];
138
139    let relative_velocity = velocity1;
140
141    // Calculate the quadratic equation coefficients
142    let a =
143        relative_velocity[0] * relative_velocity[0] + relative_velocity[1] * relative_velocity[1];
144
145    // If a is very close to 0, the circle is not moving relative to the other
146    if a < 1e-10 {
147        // Check if they are already colliding
148        let distance_squared = relative_position[0] * relative_position[0]
149            + relative_position[1] * relative_position[1];
150        let sum_of_radii = circle1.radius + circle2.radius;
151
152        if distance_squared <= sum_of_radii * sum_of_radii {
153            return Some(0.0);
154        } else {
155            return None;
156        }
157    }
158
159    let b = 2.0
160        * (relative_velocity[0] * relative_position[0]
161            + relative_velocity[1] * relative_position[1]);
162
163    let c = relative_position[0] * relative_position[0]
164        + relative_position[1] * relative_position[1]
165        - (circle1.radius + circle2.radius) * (circle1.radius + circle2.radius);
166
167    // If c <= 0, the circles are already colliding
168    if c <= 0.0 {
169        return Some(0.0);
170    }
171
172    let discriminant = b * b - 4.0 * a * c;
173
174    if discriminant < 0.0 {
175        // No collision will occur
176        return None;
177    }
178
179    // Calculate the time of collision
180    let t = (-b - discriminant.sqrt()) / (2.0 * a);
181
182    // Check if the collision occurs within the time _step
183    if t >= 0.0 && t <= time_step {
184        Some(t)
185    } else {
186        None
187    }
188}
189
190/// Tests if a moving point will collide with a static triangle within a time step
191///
192/// # Arguments
193///
194/// * `point` - The initial position of the point [x, y, z]
195/// * `velocity` - The velocity of the point [vx, vy, vz]
196/// * `triangle` - The static triangle
197/// * `time_step` - The time step to check for collision
198///
199/// # Returns
200///
201/// `Some(time)` if the point will collide with the triangle within the time step, `None` otherwise
202#[allow(dead_code)]
203pub fn continuous_point_triangle3d_collision(
204    point: &[f64; 3],
205    velocity: &[f64; 3],
206    triangle: &Triangle3D,
207    time_step: f64,
208) -> Option<f64> {
209    // Use ray-triangle intersection
210    // The ray origin is the point position, and the ray direction is the velocity
211
212    // Normalize velocity to get ray direction
213    let speed =
214        (velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2]).sqrt();
215
216    // If the point is not moving, no collision will occur
217    if speed < 1e-10 {
218        return None;
219    }
220
221    let ray_direction = [
222        velocity[0] / speed,
223        velocity[1] / speed,
224        velocity[2] / speed,
225    ];
226
227    // Calculate the intersection using ray-triangle collision
228    let intersection =
229        super::narrowphase::ray_triangle3d_collision(point, &ray_direction, triangle);
230
231    // Check if the intersection occurs within the time _step
232    match intersection {
233        Some((t, hit_point, barycentric)) => {
234            let actual_t = t / speed; // Convert from ray parameter to time
235            if actual_t >= 0.0 && actual_t <= time_step {
236                Some(actual_t)
237            } else {
238                None
239            }
240        }
241        None => None,
242    }
243}
244
245/// Tests if a moving box collides with a static box within a time step
246///
247/// # Arguments
248///
249/// * `box1` - The moving box
250/// * `velocity1` - The velocity of the moving box [vx, vy, vz]
251/// * `box2` - The static box
252/// * `time_step` - The time step to check for collision
253///
254/// # Returns
255///
256/// `Some(time)` if the boxes will collide within the time step, `None` otherwise
257#[allow(dead_code)]
258pub fn continuous_box3d_box3d_collision(
259    box1: &Box3D,
260    velocity1: &[f64; 3],
261    box2: &Box3D,
262    time_step: f64,
263) -> Option<f64> {
264    // Check if the boxes are already colliding
265    if super::narrowphase::box3d_box3d_collision(box1, box2) {
266        return Some(0.0);
267    }
268
269    // For each axis, calculate the time when the boxes would overlap along that axis
270    let mut t_entry = [f64::NEG_INFINITY; 3];
271    let mut t_exit = [f64::INFINITY; 3];
272
273    for i in 0..3 {
274        if velocity1[i] > 0.0 {
275            t_entry[i] = (box2.min[i] - box1.max[i]) / velocity1[i];
276            t_exit[i] = (box2.max[i] - box1.min[i]) / velocity1[i];
277        } else if velocity1[i] < 0.0 {
278            t_entry[i] = (box2.max[i] - box1.min[i]) / velocity1[i];
279            t_exit[i] = (box2.min[i] - box1.max[i]) / velocity1[i];
280        }
281        // If velocity is 0, the boxes can never collide along this axis
282        // unless they already overlap (which we checked earlier)
283    }
284
285    // Find the latest entry time and earliest exit time
286    let t_in = t_entry[0].max(t_entry[1]).max(t_entry[2]);
287    let t_out = t_exit[0].min(t_exit[1]).min(t_exit[2]);
288
289    // If exit time is less than entry time, or entry time is after the time step,
290    // or exit time is negative, there is no collision within the time _step
291    if t_in > t_out || t_in > time_step || t_out < 0.0 {
292        return None;
293    }
294
295    // Return the entry time if it's positive, otherwise 0 (already overlapping)
296    if t_in >= 0.0 {
297        Some(t_in)
298    } else {
299        Some(0.0)
300    }
301}
302
303/// Tests if a moving triangle collides with a static sphere within a time step
304///
305/// # Arguments
306///
307/// * `triangle` - The triangle
308/// * `velocity` - The velocity of the triangle [vx, vy, vz]
309/// * `sphere` - The static sphere
310/// * `time_step` - The time step to check for collision
311///
312/// # Returns
313///
314/// `Some(time)` if the triangle will collide with the sphere within the time step, `None` otherwise
315#[allow(dead_code)]
316pub fn continuous_triangle3d_sphere_collision(
317    triangle: &Triangle3D,
318    velocity: &[f64; 3],
319    sphere: &Sphere,
320    time_step: f64,
321) -> Option<f64> {
322    // Use the inverse problem: a static triangle and a moving sphere
323    // with the negative velocity of the triangle
324    let inverse_velocity = [-velocity[0], -velocity[1], -velocity[2]];
325
326    // Adjust the starting position of the sphere to compensate for the triangle's movement
327    let sphere_adjusted = Sphere {
328        center: sphere.center,
329        radius: sphere.radius,
330    };
331
332    // Check for initial collision
333    if super::narrowphase::sphere_triangle3d_collision(&sphere_adjusted, triangle) {
334        return Some(0.0);
335    }
336
337    // For simplicity, we'll break this down into three separate collision tests,
338    // one for each vertex of the triangle and one for each edge
339
340    // First, check for collisions between the sphere and each vertex of the triangle
341    let vertices = [triangle.v1, triangle.v2, triangle.v3];
342    let mut earliest_time = f64::INFINITY;
343    let mut collision_found = false;
344
345    for vertex in &vertices {
346        // Create a point representing the vertex
347        let point = *vertex;
348
349        // Calculate distance from the point to the sphere center
350        let dx = point[0] - sphere.center[0];
351        let dy = point[1] - sphere.center[1];
352        let dz = point[2] - sphere.center[2];
353        let dist_squared = dx * dx + dy * dy + dz * dz;
354
355        // If the point is already inside the sphere, collision at t=0
356        if dist_squared <= sphere.radius * sphere.radius {
357            return Some(0.0);
358        }
359
360        // Calculate quadratic equation for time of collision
361        let a = inverse_velocity[0] * inverse_velocity[0]
362            + inverse_velocity[1] * inverse_velocity[1]
363            + inverse_velocity[2] * inverse_velocity[2];
364
365        if a < 1e-10 {
366            continue; // Velocity is too small to consider
367        }
368
369        let b =
370            2.0 * (dx * inverse_velocity[0] + dy * inverse_velocity[1] + dz * inverse_velocity[2]);
371
372        let c = dist_squared - sphere.radius * sphere.radius;
373
374        let discriminant = b * b - 4.0 * a * c;
375
376        if discriminant >= 0.0 {
377            let t = (-b - discriminant.sqrt()) / (2.0 * a);
378
379            if t >= 0.0 && t <= time_step && t < earliest_time {
380                earliest_time = t;
381                collision_found = true;
382            }
383        }
384    }
385
386    // Check for collisions with the edges of the triangle
387    let edges = [
388        (triangle.v1, triangle.v2),
389        (triangle.v2, triangle.v3),
390        (triangle.v3, triangle.v1),
391    ];
392
393    for &(start, end) in &edges {
394        // Create a vector representing the edge
395        let edge_vector = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
396
397        let edge_length_squared = edge_vector[0] * edge_vector[0]
398            + edge_vector[1] * edge_vector[1]
399            + edge_vector[2] * edge_vector[2];
400
401        // Calculate the closest point on the line to the sphere center
402        let t0 = {
403            let v = [
404                sphere.center[0] - start[0],
405                sphere.center[1] - start[1],
406                sphere.center[2] - start[2],
407            ];
408
409            let dot = v[0] * edge_vector[0] + v[1] * edge_vector[1] + v[2] * edge_vector[2];
410
411            dot / edge_length_squared
412        };
413
414        // Clamp t0 to [0, 1] to get the closest point on the segment
415        let t0_clamped = t0.clamp(0.0, 1.0);
416
417        // Calculate the closest point on the edge
418        let closest_point = [
419            start[0] + t0_clamped * edge_vector[0],
420            start[1] + t0_clamped * edge_vector[1],
421            start[2] + t0_clamped * edge_vector[2],
422        ];
423
424        // Calculate distance from the closest point to the sphere center
425        let dx = closest_point[0] - sphere.center[0];
426        let dy = closest_point[1] - sphere.center[1];
427        let dz = closest_point[2] - sphere.center[2];
428        let dist_squared = dx * dx + dy * dy + dz * dz;
429
430        // If the closest point is already inside the sphere, collision at t=0
431        if dist_squared <= sphere.radius * sphere.radius {
432            return Some(0.0);
433        }
434
435        // Now check for future collisions
436        // We need to solve a more complex problem for edges, but for simplicity,
437        // we'll treat it approximately using the closest point
438
439        let a = inverse_velocity[0] * inverse_velocity[0]
440            + inverse_velocity[1] * inverse_velocity[1]
441            + inverse_velocity[2] * inverse_velocity[2];
442
443        if a < 1e-10 {
444            continue; // Velocity is too small to consider
445        }
446
447        let b =
448            2.0 * (dx * inverse_velocity[0] + dy * inverse_velocity[1] + dz * inverse_velocity[2]);
449
450        let c = dist_squared - sphere.radius * sphere.radius;
451
452        let discriminant = b * b - 4.0 * a * c;
453
454        if discriminant >= 0.0 {
455            let t = (-b - discriminant.sqrt()) / (2.0 * a);
456
457            if t >= 0.0 && t <= time_step && t < earliest_time {
458                earliest_time = t;
459                collision_found = true;
460            }
461        }
462    }
463
464    // Now check for collision with the triangle face
465    // This is a complex problem, but we'll use the approach of finding when
466    // the sphere's center crosses the triangle's plane minus the radius
467
468    // Calculate the triangle's normal
469    let normal = {
470        let edge1 = [
471            triangle.v2[0] - triangle.v1[0],
472            triangle.v2[1] - triangle.v1[1],
473            triangle.v2[2] - triangle.v1[2],
474        ];
475
476        let edge2 = [
477            triangle.v3[0] - triangle.v1[0],
478            triangle.v3[1] - triangle.v1[1],
479            triangle.v3[2] - triangle.v1[2],
480        ];
481
482        // Cross product
483        let cross = [
484            edge1[1] * edge2[2] - edge1[2] * edge2[1],
485            edge1[2] * edge2[0] - edge1[0] * edge2[2],
486            edge1[0] * edge2[1] - edge1[1] * edge2[0],
487        ];
488
489        // Normalize
490        let length = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
491        if length < 1e-10 {
492            [0.0, 0.0, 0.0] // Degenerate triangle
493        } else {
494            [cross[0] / length, cross[1] / length, cross[2] / length]
495        }
496    };
497
498    if normal[0] == 0.0 && normal[1] == 0.0 && normal[2] == 0.0 {
499        // Degenerate triangle
500        if collision_found {
501            return Some(earliest_time);
502        }
503        return None;
504    }
505
506    // Calculate distance from sphere center to triangle plane
507    let plane_dist = (sphere.center[0] - triangle.v1[0]) * normal[0]
508        + (sphere.center[1] - triangle.v1[1]) * normal[1]
509        + (sphere.center[2] - triangle.v1[2]) * normal[2];
510
511    // Calculate speed of approach to the plane
512    let approach_speed = -(inverse_velocity[0] * normal[0]
513        + inverse_velocity[1] * normal[1]
514        + inverse_velocity[2] * normal[2]);
515
516    if approach_speed.abs() > 1e-10 {
517        // Time until the sphere center is at a distance of radius from the plane
518        let t_plane = (plane_dist.abs() - sphere.radius) / approach_speed;
519
520        if t_plane >= 0.0 && t_plane <= time_step && t_plane < earliest_time {
521            // At this time, the sphere is at a distance of radius from the plane
522            // Now check if the projection of the sphere center onto the plane is inside the triangle
523
524            let projected_center = [
525                sphere.center[0] + inverse_velocity[0] * t_plane
526                    - normal[0] * sphere.radius * plane_dist.signum(),
527                sphere.center[1] + inverse_velocity[1] * t_plane
528                    - normal[1] * sphere.radius * plane_dist.signum(),
529                sphere.center[2] + inverse_velocity[2] * t_plane
530                    - normal[2] * sphere.radius * plane_dist.signum(),
531            ];
532
533            if super::narrowphase::point_triangle3d_collision(&projected_center, triangle) {
534                earliest_time = t_plane;
535                collision_found = true;
536            }
537        }
538    }
539
540    if collision_found {
541        Some(earliest_time)
542    } else {
543        None
544    }
545}
546
547/// Tests if a moving sphere collides with a static triangle within a time step
548///
549/// # Arguments
550///
551/// * `sphere` - The sphere
552/// * `velocity` - The velocity of the sphere [vx, vy, vz]
553/// * `triangle` - The static triangle
554/// * `time_step` - The time step to check for collision
555///
556/// # Returns
557///
558/// `Some(time)` if the sphere will collide with the triangle within the time step, `None` otherwise
559#[allow(dead_code)]
560pub fn continuous_sphere_triangle3d_collision(
561    sphere: &Sphere,
562    velocity: &[f64; 3],
563    triangle: &Triangle3D,
564    time_step: f64,
565) -> Option<f64> {
566    // This is the same problem as a moving triangle with the negative velocity
567    // and a static sphere
568    let inverse_velocity = [-velocity[0], -velocity[1], -velocity[2]];
569    continuous_triangle3d_sphere_collision(triangle, &inverse_velocity, sphere, time_step)
570}