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