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
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn signed_distance_above() {
190 let plane = HalfSpace::<3>::ground(1, 0.0); let point = SVector::from([0.0, 5.0, 0.0]);
192 assert!((plane.signed_distance(&point) - 5.0).abs() < 1e-12);
193 }
194
195 #[test]
196 fn signed_distance_below() {
197 let plane = HalfSpace::<3>::ground(1, 0.0);
198 let point = SVector::from([0.0, -3.0, 0.0]);
199 assert!((plane.signed_distance(&point) - (-3.0)).abs() < 1e-12);
200 }
201
202 #[test]
203 fn project_onto_plane() {
204 let plane = HalfSpace::<3>::ground(1, 0.0);
205 let point = SVector::from([3.0, 5.0, 7.0]);
206 let proj = plane.project(&point);
207 assert!((proj[0] - 3.0).abs() < 1e-12);
208 assert!((proj[1] - 0.0).abs() < 1e-12);
209 assert!((proj[2] - 7.0).abs() < 1e-12);
210 }
211
212 #[test]
213 fn sphere_contact_resting() {
214 let plane = HalfSpace::<3>::ground(1, 0.0);
215 let center = SVector::from([0.0, 0.5, 0.0]); let (contact, depth) = plane.contact_sphere(¢er, 1.0).unwrap();
217 assert!((depth - 0.5).abs() < 1e-12, "depth = {depth}");
218 assert!((contact[1] - 0.0).abs() < 1e-12, "contact Y = {}", contact[1]);
219 }
220
221 #[test]
222 fn sphere_no_contact() {
223 let plane = HalfSpace::<3>::ground(1, 0.0);
224 let center = SVector::from([0.0, 2.0, 0.0]);
225 assert!(plane.contact_sphere(¢er, 1.0).is_none());
226 }
227
228 #[test]
229 fn capsule_two_contacts() {
230 let plane = HalfSpace::<3>::ground(1, 0.0);
231 let pos = SVector::from([0.0, 0.3, 0.0]);
233 let contacts = plane.contact_capsule(&pos, 2.0, 0.5, 0);
234 assert_eq!(contacts.len(), 2);
236 for (_, depth) in &contacts {
237 assert!(
238 (depth - 0.2).abs() < 1e-10,
239 "capsule contact depth = {depth}"
240 );
241 }
242 }
243
244 #[test]
245 fn box_contact_on_plane() {
246 let plane = HalfSpace::<3>::ground(1, 0.0);
247 let pos = SVector::from([0.0, 0.5, 0.0]);
249 let contacts = plane.contact_box(&pos, &[1.0, 1.0, 1.0]);
250 assert_eq!(contacts.len(), 4, "expected 4 bottom vertices to penetrate");
252 for (_, depth) in &contacts {
253 assert!(
254 (depth - 0.5).abs() < 1e-10,
255 "box vertex depth = {depth}"
256 );
257 }
258 }
259
260 #[test]
261 fn box_no_contact() {
262 let plane = HalfSpace::<3>::ground(1, 0.0);
263 let pos = SVector::from([0.0, 5.0, 0.0]);
264 let contacts = plane.contact_box(&pos, &[1.0, 1.0, 1.0]);
265 assert!(contacts.is_empty());
266 }
267
268 #[test]
269 fn halfspace_4d() {
270 let plane = HalfSpace::<4>::ground(3, 0.0); let center = SVector::from([0.0, 0.0, 0.0, 0.8]);
272 let contact = plane.contact_sphere(¢er, 1.0);
273 assert!(contact.is_some());
274 let (_, depth) = contact.unwrap();
275 assert!((depth - 0.2).abs() < 1e-12, "4D depth = {depth}");
276 }
277
278 #[test]
279 fn halfspace_offset() {
280 let plane = HalfSpace::<3>::ground(1, 2.0); let center = SVector::from([0.0, 2.5, 0.0]);
282 let contact = plane.contact_sphere(¢er, 1.0);
283 assert!(contact.is_some());
284 let (_, depth) = contact.unwrap();
285 assert!((depth - 0.5).abs() < 1e-12, "offset plane depth = {depth}");
286 }
287
288 #[test]
289 fn halfspace_2d() {
290 let plane = HalfSpace::<2>::ground(1, 0.0);
291 let center = SVector::from([3.0, 0.5]);
292 let (contact, depth) = plane.contact_sphere(¢er, 1.0).unwrap();
293 assert!((depth - 0.5).abs() < 1e-12);
294 assert!((contact[0] - 3.0).abs() < 1e-12);
295 }
296}