1use crate::point::Point;
17use crate::shape::Shape;
18use nalgebra::SVector;
19
20const HALFSPACE_EXTENT: f64 = 1e6;
22
23#[derive(Clone, Copy, Debug)]
28pub struct HalfSpace<const D: usize> {
29 pub normal: SVector<f64, D>,
31 pub offset: f64,
34}
35
36impl<const D: usize> HalfSpace<D> {
37 pub fn new(normal: SVector<f64, D>, offset: f64) -> Self {
40 Self { normal, offset }
41 }
42
43 pub fn ground(axis: usize, height: f64) -> Self {
46 let mut normal = SVector::zeros();
47 normal[axis] = 1.0;
48 Self {
49 normal,
50 offset: height,
51 }
52 }
53
54 #[inline]
58 pub fn signed_distance(&self, point: &SVector<f64, D>) -> f64 {
59 self.normal.dot(point) - self.offset
60 }
61
62 pub fn project(&self, point: &SVector<f64, D>) -> SVector<f64, D> {
64 point - self.normal * self.signed_distance(point)
65 }
66
67 pub fn contact_sphere(
76 &self,
77 sphere_center: &SVector<f64, D>,
78 sphere_radius: f64,
79 ) -> Option<(SVector<f64, D>, f64)> {
80 let dist = self.signed_distance(sphere_center);
81 let depth = sphere_radius - dist;
82 if depth <= 0.0 {
83 return None;
84 }
85 let contact = sphere_center - self.normal * dist;
87 Some((contact, depth))
88 }
89
90 pub fn contact_capsule(
95 &self,
96 capsule_pos: &SVector<f64, D>,
97 half_height: f64,
98 radius: f64,
99 axis: usize,
100 ) -> Vec<(SVector<f64, D>, f64)> {
101 let mut contacts = Vec::new();
102
103 let mut axis_vec = SVector::zeros();
105 axis_vec[axis] = 1.0;
106
107 let center_a = capsule_pos + axis_vec * half_height;
108 let center_b = capsule_pos - axis_vec * half_height;
109
110 if let Some(c) = self.contact_sphere(¢er_a, radius) {
111 contacts.push(c);
112 }
113 if let Some(c) = self.contact_sphere(¢er_b, radius) {
114 contacts.push(c);
115 }
116
117 contacts
118 }
119
120 pub fn contact_box(
125 &self,
126 box_pos: &SVector<f64, D>,
127 half_extents: &[f64; D],
128 ) -> Vec<(SVector<f64, D>, f64)> {
129 let mut contacts = Vec::new();
130
131 let num_vertices = 1usize << D;
133 for bits in 0..num_vertices {
134 let mut vertex = *box_pos;
135 for axis in 0..D {
136 if bits & (1 << axis) != 0 {
137 vertex[axis] += half_extents[axis];
138 } else {
139 vertex[axis] -= half_extents[axis];
140 }
141 }
142
143 let dist = self.signed_distance(&vertex);
144 if dist < 0.0 {
145 let contact = self.project(&vertex);
146 contacts.push((contact, -dist));
147 }
148 }
149
150 contacts
151 }
152}
153
154impl<const D: usize> Shape<D> for HalfSpace<D> {
155 fn support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
162 let dot = direction.dot(&self.normal);
163 if dot >= 0.0 {
164 let tangent = direction - self.normal * dot;
167 let t_norm = tangent.norm();
168 if t_norm > 1e-15 {
169 self.normal * self.offset + tangent / t_norm * HALFSPACE_EXTENT
170 } else {
171 self.normal * self.offset
172 }
173 } else {
174 self.normal * (self.offset - HALFSPACE_EXTENT)
176 }
177 }
178
179 fn bounding_sphere(&self) -> (Point<D>, f64) {
180 (Point::origin(), HALFSPACE_EXTENT)
181 }
182
183 fn as_any(&self) -> &dyn std::any::Any { self }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn signed_distance_above() {
192 let plane = HalfSpace::<3>::ground(1, 0.0); let point = SVector::from([0.0, 5.0, 0.0]);
194 assert!((plane.signed_distance(&point) - 5.0).abs() < 1e-12);
195 }
196
197 #[test]
198 fn signed_distance_below() {
199 let plane = HalfSpace::<3>::ground(1, 0.0);
200 let point = SVector::from([0.0, -3.0, 0.0]);
201 assert!((plane.signed_distance(&point) - (-3.0)).abs() < 1e-12);
202 }
203
204 #[test]
205 fn project_onto_plane() {
206 let plane = HalfSpace::<3>::ground(1, 0.0);
207 let point = SVector::from([3.0, 5.0, 7.0]);
208 let proj = plane.project(&point);
209 assert!((proj[0] - 3.0).abs() < 1e-12);
210 assert!((proj[1] - 0.0).abs() < 1e-12);
211 assert!((proj[2] - 7.0).abs() < 1e-12);
212 }
213
214 #[test]
215 fn sphere_contact_resting() {
216 let plane = HalfSpace::<3>::ground(1, 0.0);
217 let center = SVector::from([0.0, 0.5, 0.0]); let (contact, depth) = plane.contact_sphere(¢er, 1.0).unwrap();
219 assert!((depth - 0.5).abs() < 1e-12, "depth = {depth}");
220 assert!((contact[1] - 0.0).abs() < 1e-12, "contact Y = {}", contact[1]);
221 }
222
223 #[test]
224 fn sphere_no_contact() {
225 let plane = HalfSpace::<3>::ground(1, 0.0);
226 let center = SVector::from([0.0, 2.0, 0.0]);
227 assert!(plane.contact_sphere(¢er, 1.0).is_none());
228 }
229
230 #[test]
231 fn capsule_two_contacts() {
232 let plane = HalfSpace::<3>::ground(1, 0.0);
233 let pos = SVector::from([0.0, 0.3, 0.0]);
235 let contacts = plane.contact_capsule(&pos, 2.0, 0.5, 0);
236 assert_eq!(contacts.len(), 2);
238 for (_, depth) in &contacts {
239 assert!(
240 (depth - 0.2).abs() < 1e-10,
241 "capsule contact depth = {depth}"
242 );
243 }
244 }
245
246 #[test]
247 fn box_contact_on_plane() {
248 let plane = HalfSpace::<3>::ground(1, 0.0);
249 let pos = SVector::from([0.0, 0.5, 0.0]);
251 let contacts = plane.contact_box(&pos, &[1.0, 1.0, 1.0]);
252 assert_eq!(contacts.len(), 4, "expected 4 bottom vertices to penetrate");
254 for (_, depth) in &contacts {
255 assert!(
256 (depth - 0.5).abs() < 1e-10,
257 "box vertex depth = {depth}"
258 );
259 }
260 }
261
262 #[test]
263 fn box_no_contact() {
264 let plane = HalfSpace::<3>::ground(1, 0.0);
265 let pos = SVector::from([0.0, 5.0, 0.0]);
266 let contacts = plane.contact_box(&pos, &[1.0, 1.0, 1.0]);
267 assert!(contacts.is_empty());
268 }
269
270 #[test]
271 fn halfspace_4d() {
272 let plane = HalfSpace::<4>::ground(3, 0.0); let center = SVector::from([0.0, 0.0, 0.0, 0.8]);
274 let contact = plane.contact_sphere(¢er, 1.0);
275 assert!(contact.is_some());
276 let (_, depth) = contact.unwrap();
277 assert!((depth - 0.2).abs() < 1e-12, "4D depth = {depth}");
278 }
279
280 #[test]
281 fn halfspace_offset() {
282 let plane = HalfSpace::<3>::ground(1, 2.0); let center = SVector::from([0.0, 2.5, 0.0]);
284 let contact = plane.contact_sphere(¢er, 1.0);
285 assert!(contact.is_some());
286 let (_, depth) = contact.unwrap();
287 assert!((depth - 0.5).abs() < 1e-12, "offset plane depth = {depth}");
288 }
289
290 #[test]
291 fn halfspace_2d() {
292 let plane = HalfSpace::<2>::ground(1, 0.0);
293 let center = SVector::from([3.0, 0.5]);
294 let (contact, depth) = plane.contact_sphere(¢er, 1.0).unwrap();
295 assert!((depth - 0.5).abs() < 1e-12);
296 assert!((contact[0] - 3.0).abs() < 1e-12);
297 }
298}