1use crate::{ClosestPoint, LineSegment, Plane, Ray, Sphere, Triangle};
2use mini_math::{NearlyEqual, Point, Vector3};
3
4#[derive(PartialEq, Debug)]
6pub struct Contact {
7 pub point: Point,
9 pub normal: Vector3,
11 pub overlap: f32,
13}
14
15impl NearlyEqual for &Contact {
16 fn nearly_equals(self, rhs: Self) -> bool {
17 self.point.nearly_equals(&rhs.point)
18 && self.normal.nearly_equals(&rhs.normal)
19 && self.overlap.nearly_equals(rhs.overlap)
20 }
21}
22
23impl Contact {
24 fn new(point: Point, normal: Vector3, overlap: f32) -> Self {
25 Self {
26 point,
27 normal,
28 overlap,
29 }
30 }
31}
32
33pub trait Collision<Rhs> {
35 fn collides(&self, rhs: &Rhs) -> Option<Contact>;
37}
38
39impl Collision<Sphere> for Sphere {
40 fn collides(&self, sphere: &Sphere) -> Option<Contact> {
41 let combined_radius = self.radius + sphere.radius;
42 let diff = self.center - sphere.center;
43 let distance_squared = diff.magnitude_squared();
44 if distance_squared > combined_radius * combined_radius {
45 None
46 } else {
47 let distance = distance_squared.sqrt();
48 let normal = diff / distance;
49
50 Some(Contact::new(
51 sphere.center + normal * sphere.radius,
52 normal,
53 combined_radius - distance,
54 ))
55 }
56 }
57}
58
59impl Collision<Triangle> for Sphere {
60 fn collides(&self, triangle: &Triangle) -> Option<Contact> {
61 let plane = Plane::from(triangle);
62
63 let p = plane.closest_point(&self.center);
64 let distance_from_plane_squared = (p - self.center).magnitude_squared();
65
66 if distance_from_plane_squared > self.radius * self.radius {
67 None
68 } else {
69 let q = triangle.closest_point(&self.center);
70 let diff = q - self.center;
71 let overlap = self.radius - diff.magnitude();
72 if overlap < 0.0 {
73 None
74 } else {
75 Some(Contact::new(q, plane.normal, overlap))
76 }
77 }
78 }
79}
80
81impl Collision<Triangle> for Ray {
82 fn collides(&self, triangle: &Triangle) -> Option<Contact> {
83 let plane = Plane::from(triangle);
84
85 let n_dot_r = plane.normal.dot(self.direction);
86 if n_dot_r.abs() < std::f32::EPSILON {
88 return None;
89 }
90
91 let d = plane.normal.dot(Vector3::from(triangle.a));
92 let e = plane.normal.dot(Vector3::from(self.origin));
93 let t = (e + d) / n_dot_r;
94
95 if t > 0.0 {
97 return None;
98 }
99
100 let intersection_point = self.origin + self.direction * -t;
101 if triangle.coplanar_point_inside(intersection_point) {
102 Some(Contact::new(intersection_point, plane.normal, 0.0))
103 } else {
104 None
105 }
106 }
107}
108
109impl Collision<Triangle> for LineSegment {
110 fn collides(&self, triangle: &Triangle) -> Option<Contact> {
111 let plane = Plane::from(triangle);
112
113 let mut direction = self.end - self.start;
114 let length = direction.magnitude();
115 direction /= length;
116
117 let n_dot_r = plane.normal.dot(direction);
118 if n_dot_r.abs() < std::f32::EPSILON {
120 return None;
121 }
122
123 let d = plane.normal.dot(Vector3::from(triangle.a));
124 let e = plane.normal.dot(Vector3::from(self.start));
125 let t = (e + d) / n_dot_r;
126
127 if t > 0.0 || t < -length {
129 return None;
130 }
131
132 let intersection_point = self.start + direction * -t;
133 if triangle.coplanar_point_inside(intersection_point) {
134 Some(Contact::new(intersection_point, plane.normal, 0.0))
135 } else {
136 None
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use mini_math::{Point, Vector3};
145
146 #[test]
147 fn test_sphere_sphere_collision() {
148 let a = Sphere::new(Point::zero(), 1.0);
149 let b = Sphere::new(Point::new(0.0, 1.5, 0.0), 1.0);
150
151 assert_eq!(
152 b.collides(&a),
153 Some(Contact::new(
154 Point::new(0.0, 1.0, 0.0),
155 Vector3::new(0.0, 1.0, 0.0),
156 0.5
157 ))
158 );
159 }
160
161 #[test]
162 fn test_sphere_triangle_collision() {
163 let a = Triangle::new(
164 Point::new(-1.0, 0.0, -1.0),
165 Point::new(1.0, 0.0, -1.0),
166 Point::new(0.0, 0.0, 1.0),
167 );
168 let b = Sphere::new(Point::new(0.0, 0.75, 0.0), 1.0);
169
170 assert_eq!(
171 b.collides(&a),
172 Some(Contact::new(
173 Point::new(0.0, 0.0, 0.0),
174 Vector3::new(0.0, 1.0, 0.0),
175 0.25
176 ))
177 );
178
179 let b = Sphere::new(Point::new(0.0, 1.75, 0.0), 1.0);
180 assert_eq!(b.collides(&a), None);
181
182 let b = Sphere::new(Point::new(0.0, -1.75, 0.0), 1.0);
183 assert_eq!(b.collides(&a), None);
184
185 let b = Sphere::new(Point::new(-3.0, 0.0, -3.0), 1.0);
186 assert_eq!(b.collides(&a), None);
187 }
188
189 #[test]
190 fn test_triangle_ray_collision() {
191 let triangle = Triangle::new(
192 Point::new(-1.0, 0.0, 0.0),
193 Point::new(1.0, 0.0, 0.0),
194 Point::new(0.0, 0.0, 1.0),
195 );
196
197 let ray = Ray::new(Point::new(0.0, 1.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
199 assert_eq!(ray.collides(&triangle), None);
200
201 let ray = Ray::new(Point::new(0.0, 1.0, 0.0), Vector3::new(0.0, 1.0, 0.0));
203 assert_eq!(ray.collides(&triangle), None);
204
205 let ray = Ray::new(Point::new(0.0, -1.0, 0.0), Vector3::new(0.0, -1.0, 0.0));
207 assert_eq!(ray.collides(&triangle), None);
208
209 let ray = Ray::new(Point::new(3.0, 1.0, 3.0), Vector3::new(0.0, -1.0, 0.0));
211 assert_eq!(ray.collides(&triangle), None);
212
213 let ray = Ray::new(Point::new(0.0, 1.0, 0.0), Vector3::new(0.0, -1.0, 0.0));
215 assert_eq!(
216 ray.collides(&triangle),
217 Some(Contact::new(
218 Point::new(0.0, 0.0, 0.0),
219 Vector3::new(0.0, 1.0, 0.0),
220 0.0
221 ))
222 );
223
224 let ray = Ray::new(
226 Point::new(-0.5, -1.0, 0.0),
227 Vector3::new(0.5, 1.0, 0.0).normalized(),
228 );
229 assert_eq!(
230 ray.collides(&triangle),
231 Some(Contact::new(
232 Point::new(0.0, 0.0, 0.0),
233 Vector3::new(0.0, 1.0, 0.0),
234 0.0
235 ))
236 );
237 }
238}