parry3d/shape/cuboid.rs
1//! Support mapping based Cuboid shape.
2
3use crate::math::{Point, Real, Vector};
4#[cfg(feature = "dim3")]
5use crate::shape::Segment;
6use crate::shape::{FeatureId, PackedFeatureId, PolygonalFeature, SupportMap};
7use crate::utils::WSign;
8use na::Unit;
9
10#[cfg(feature = "rkyv")]
11use rkyv::{bytecheck, CheckBytes};
12
13/// Shape of a box.
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
16#[cfg_attr(
17 feature = "rkyv",
18 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
19 archive(as = "Self")
20)]
21#[derive(PartialEq, Debug, Copy, Clone)]
22#[repr(C)]
23pub struct Cuboid {
24 /// The half-extents of the cuboid.
25 pub half_extents: Vector<Real>,
26}
27
28impl Cuboid {
29 /// Creates a new box from its half-extents. Half-extents are the box half-width along each
30 /// axis. Each half-extent must be positive.
31 #[inline]
32 pub fn new(half_extents: Vector<Real>) -> Cuboid {
33 Cuboid { half_extents }
34 }
35
36 /// Computes a scaled version of this cuboid.
37 pub fn scaled(self, scale: &Vector<Real>) -> Self {
38 let new_hext = self.half_extents.component_mul(scale);
39 Self {
40 half_extents: new_hext,
41 }
42 }
43
44 /// Return the id of the vertex of this cuboid with a normal that maximizes
45 /// the dot product with `dir`.
46 #[cfg(feature = "dim2")]
47 pub fn vertex_feature_id(vertex: Point<Real>) -> u32 {
48 // TODO: is this still correct with the f64 version?
49 #[allow(clippy::unnecessary_cast)] // Unnecessary for f32 but necessary for f64.
50 {
51 ((vertex.x.to_bits() >> 31) & 0b001 | (vertex.y.to_bits() >> 30) & 0b010) as u32
52 }
53 }
54
55 /// Return the feature of this cuboid with a normal that maximizes
56 /// the dot product with `dir`.
57 #[cfg(feature = "dim2")]
58 pub fn support_feature(&self, local_dir: Vector<Real>) -> PolygonalFeature {
59 // In 2D, it is best for stability to always return a face.
60 // It won't have any notable impact on performances anyway.
61 self.support_face(local_dir)
62 }
63
64 /// Return the face of this cuboid with a normal that maximizes
65 /// the dot product with `local_dir`.
66 #[cfg(feature = "dim2")]
67 pub fn support_face(&self, local_dir: Vector<Real>) -> PolygonalFeature {
68 let he = self.half_extents;
69 let i = local_dir.iamin();
70 let j = (i + 1) % 2;
71 let mut a = Point::origin();
72 a[i] = he[i];
73 a[j] = he[j].copysign(local_dir[j]);
74
75 let mut b = a;
76 b[i] = -he[i];
77
78 let vid1 = Self::vertex_feature_id(a);
79 let vid2 = Self::vertex_feature_id(b);
80 let fid = (vid1.max(vid2) << 2) | vid1.min(vid2) | 0b11_00_00;
81
82 PolygonalFeature {
83 vertices: [a, b],
84 vids: PackedFeatureId::vertices([vid1, vid2]),
85 fid: PackedFeatureId::face(fid),
86 num_vertices: 2,
87 }
88 }
89
90 /// Return the face of this cuboid with a normal that maximizes
91 /// the dot product with `local_dir`.
92 #[cfg(feature = "dim3")]
93 pub fn support_feature(&self, local_dir: Vector<Real>) -> PolygonalFeature {
94 // TODO: this should actually return the feature.
95 // And we should change all the callers of this method to use
96 // `.support_face` instead of this method to preserve their old behavior.
97 self.support_face(local_dir)
98 /*
99 const MAX_DOT_THRESHOLD: Real = crate::utils::COS_10_DEGREES;
100 const MIN_DOT_THRESHOLD: Real = 1.0 - MAX_DOT_THRESHOLD;
101
102 let amax = local_dir.amax();
103 let amin = local_dir.amin();
104
105 if amax > MAX_DOT_THRESHOLD {
106 // Support face.
107 CuboidFeature::Face(support_face(self, local_dir))
108 } else if amin < MIN_DOT_THRESHOLD {
109 // Support edge.
110 CuboidFeature::Edge(support_edge(self, local_dir))
111 } else {
112 // Support vertex.
113 CuboidFeature::Vertex(support_vertex(self, local_dir))
114 }
115 */
116 }
117
118 // #[cfg(feature = "dim3")
119 // pub(crate) fn support_vertex(&self, local_dir: Vector<Real>) -> CuboidFeatureVertex {
120 // let vertex = local_support_point(self, local_dir);
121 // let vid = vertex_feature_id(vertex);
122 //
123 // CuboidFeatureVertex { vertex, vid }
124 // }
125
126 /// Return the edge segment of this cuboid with a normal cone containing
127 /// a direction that that maximizes the dot product with `local_dir`.
128 #[cfg(feature = "dim3")]
129 pub fn local_support_edge_segment(&self, local_dir: Vector<Real>) -> Segment {
130 let he = self.half_extents;
131 let i = local_dir.iamin();
132 let j = (i + 1) % 3;
133 let k = (i + 2) % 3;
134 let mut a = Point::origin();
135 a[i] = he[i];
136 a[j] = he[j].copysign(local_dir[j]);
137 a[k] = he[k].copysign(local_dir[k]);
138
139 let mut b = a;
140 b[i] = -he[i];
141
142 Segment::new(a, b)
143 }
144
145 /// Computes the face with a normal that maximizes the dot-product with `local_dir`.
146 #[cfg(feature = "dim3")]
147 pub fn support_face(&self, local_dir: Vector<Real>) -> PolygonalFeature {
148 // NOTE: can we use the orthonormal basis of local_dir
149 // to make this AoSoA friendly?
150 let he = self.half_extents;
151 let iamax = local_dir.iamax();
152 #[expect(clippy::unnecessary_cast)]
153 let sign = (1.0 as Real).copysign(local_dir[iamax]);
154
155 let vertices = match iamax {
156 0 => [
157 Point::new(he.x * sign, he.y, he.z),
158 Point::new(he.x * sign, -he.y, he.z),
159 Point::new(he.x * sign, -he.y, -he.z),
160 Point::new(he.x * sign, he.y, -he.z),
161 ],
162 1 => [
163 Point::new(he.x, he.y * sign, he.z),
164 Point::new(-he.x, he.y * sign, he.z),
165 Point::new(-he.x, he.y * sign, -he.z),
166 Point::new(he.x, he.y * sign, -he.z),
167 ],
168 2 => [
169 Point::new(he.x, he.y, he.z * sign),
170 Point::new(he.x, -he.y, he.z * sign),
171 Point::new(-he.x, -he.y, he.z * sign),
172 Point::new(-he.x, he.y, he.z * sign),
173 ],
174 _ => unreachable!(),
175 };
176
177 pub fn vid(i: u32) -> u32 {
178 // Each vertex has an even feature id.
179 i * 2
180 }
181
182 let sign_index = ((sign as i8 + 1) / 2) as usize;
183 // The vertex id as numbered depending on the sign of the vertex
184 // component. A + sign means the corresponding bit is 0 while a -
185 // sign means the corresponding bit is 1.
186 // For exampl the vertex [2.0, -1.0, -3.0] has the id 0b011
187 let vids = match iamax {
188 0 => [
189 [vid(0b000), vid(0b010), vid(0b011), vid(0b001)],
190 [vid(0b100), vid(0b110), vid(0b111), vid(0b101)],
191 ][sign_index],
192 1 => [
193 [vid(0b000), vid(0b100), vid(0b101), vid(0b001)],
194 [vid(0b010), vid(0b110), vid(0b111), vid(0b011)],
195 ][sign_index],
196 2 => [
197 [vid(0b000), vid(0b010), vid(0b110), vid(0b100)],
198 [vid(0b001), vid(0b011), vid(0b111), vid(0b101)],
199 ][sign_index],
200 _ => unreachable!(),
201 };
202
203 // The feature ids of edges is obtained from the vertex ids
204 // of their endpoints.
205 // Assuming vid1 > vid2, we do: (vid1 << 3) | vid2 | 0b11000000
206 //
207 let eids = match iamax {
208 0 => [
209 [0b11_010_000, 0b11_011_010, 0b11_011_001, 0b11_001_000],
210 [0b11_110_100, 0b11_111_110, 0b11_111_101, 0b11_101_100],
211 ][sign_index],
212 1 => [
213 [0b11_100_000, 0b11_101_100, 0b11_101_001, 0b11_001_000],
214 [0b11_110_010, 0b11_111_110, 0b11_111_011, 0b11_011_010],
215 ][sign_index],
216 2 => [
217 [0b11_010_000, 0b11_110_010, 0b11_110_100, 0b11_100_000],
218 [0b11_011_001, 0b11_111_011, 0b11_111_101, 0b11_101_001],
219 ][sign_index],
220 _ => unreachable!(),
221 };
222
223 // The face with normals [x, y, z] are numbered [10, 11, 12].
224 // The face with negated normals are numbered [13, 14, 15].
225 let fid = iamax + sign_index * 3 + 10;
226
227 PolygonalFeature {
228 vertices,
229 vids: PackedFeatureId::vertices(vids),
230 eids: PackedFeatureId::edges(eids),
231 fid: PackedFeatureId::face(fid as u32),
232 num_vertices: 4,
233 }
234 }
235
236 /// The normal of the given feature of this shape.
237 #[cfg(feature = "dim2")]
238 pub fn feature_normal(&self, feature: FeatureId) -> Option<Unit<Vector<Real>>> {
239 match feature {
240 FeatureId::Face(id) => {
241 let mut dir: Vector<Real> = na::zero();
242
243 if id < 2 {
244 dir[id as usize] = 1.0;
245 } else {
246 dir[id as usize - 2] = -1.0;
247 }
248 Some(Unit::new_unchecked(dir))
249 }
250 FeatureId::Vertex(id) => {
251 let mut dir: Vector<Real> = na::zero();
252
253 match id {
254 0b00 => {
255 dir[0] = 1.0;
256 dir[1] = 1.0;
257 }
258 0b01 => {
259 dir[1] = 1.0;
260 dir[0] = -1.0;
261 }
262 0b11 => {
263 dir[0] = -1.0;
264 dir[1] = -1.0;
265 }
266 0b10 => {
267 dir[1] = -1.0;
268 dir[0] = 1.0;
269 }
270 _ => return None,
271 }
272
273 Some(Unit::new_normalize(dir))
274 }
275 _ => None,
276 }
277 }
278
279 /// The normal of the given feature of this shape.
280 #[cfg(feature = "dim3")]
281 pub fn feature_normal(&self, feature: FeatureId) -> Option<Unit<Vector<Real>>> {
282 match feature {
283 FeatureId::Face(id) => {
284 let mut dir: Vector<Real> = na::zero();
285
286 if id < 3 {
287 dir[id as usize] = 1.0;
288 } else {
289 dir[id as usize - 3] = -1.0;
290 }
291 Some(Unit::new_unchecked(dir))
292 }
293 FeatureId::Edge(id) => {
294 let edge = id & 0b011;
295 let face1 = (edge + 1) % 3;
296 let face2 = (edge + 2) % 3;
297 let signs = id >> 2;
298
299 let mut dir: Vector<Real> = na::zero();
300
301 if signs & (1 << face1) != 0 {
302 dir[face1 as usize] = -1.0
303 } else {
304 dir[face1 as usize] = 1.0
305 }
306
307 if signs & (1 << face2) != 0 {
308 dir[face2 as usize] = -1.0
309 } else {
310 dir[face2 as usize] = 1.0;
311 }
312
313 Some(Unit::new_normalize(dir))
314 }
315 FeatureId::Vertex(id) => {
316 let mut dir: Vector<Real> = na::zero();
317 for i in 0..3 {
318 if id & (1 << i) != 0 {
319 dir[i] = -1.0;
320 } else {
321 dir[i] = 1.0
322 }
323 }
324
325 Some(Unit::new_normalize(dir))
326 }
327 _ => None,
328 }
329 }
330}
331
332impl SupportMap for Cuboid {
333 #[inline]
334 fn local_support_point(&self, dir: &Vector<Real>) -> Point<Real> {
335 dir.copy_sign_to(self.half_extents).into()
336 }
337}
338
339/*
340impl ConvexPolyhedron for Cuboid {
341 fn vertex(&self, id: FeatureId) -> Point<Real> {
342 let vid = id.unwrap_vertex();
343 let mut res = self.half_extents;
344
345 for i in 0..DIM {
346 if vid & (1 << i) != 0 {
347 res[i] = -res[i]
348 }
349 }
350
351 Point::from(res)
352 }
353
354 #[cfg(feature = "dim3")]
355 fn edge(&self, id: FeatureId) -> (Point<Real>, Point<Real>, FeatureId, FeatureId) {
356 let eid = id.unwrap_edge();
357 let mut res = self.half_extents;
358
359 let edge_i = eid & 0b11;
360 let vertex_i = eid >> 2;
361
362 for i in 0..DIM {
363 if i as u32 != edge_i && (vertex_i & (1 << i) != 0) {
364 res[i] = -res[i]
365 }
366 }
367
368 let p1 = Point::from(res);
369 res[edge_i as usize] = -res[edge_i as usize];
370 let p2 = Point::from(res);
371 let vid1 = FeatureId::Vertex(vertex_i & !(1 << edge_i));
372 let vid2 = FeatureId::Vertex(vertex_i | (1 << edge_i));
373
374 (p1, p2, vid1, vid2)
375 }
376
377 fn face(&self, id: FeatureId, out: &mut ConvexPolygonalFeature) {
378 out.clear();
379
380 let i = id.unwrap_face() as usize;
381 let i1;
382 let sign;
383
384 if i < DIM {
385 i1 = i;
386 sign = 1.0;
387 } else {
388 i1 = i - DIM;
389 sign = -1.0;
390 }
391
392 #[cfg(feature = "dim2")]
393 {
394 let i2 = (i1 + 1) % 2;
395
396 let mut vertex = self.half_extents;
397 vertex[i1] *= sign;
398 vertex[i2] *= if i1 == 0 { -sign } else { sign };
399
400 let p1 = Point::from(vertex);
401 vertex[i2] = -vertex[i2];
402 let p2 = Point::from(vertex);
403
404 let mut vertex_id1 = if sign < 0.0 {
405 1 << i1
406 } else {
407 0
408 };
409 let mut vertex_id2 = vertex_id1;
410 if p1[i2] < 0.0 {
411 vertex_id1 |= 1 << i2;
412 } else {
413 vertex_id2 |= 1 << i2;
414 }
415
416 out.push(p1, FeatureId::Vertex(vertex_id1));
417 out.push(p2, FeatureId::Vertex(vertex_id2));
418
419 let mut normal: Vector<Real> = na::zero();
420 normal[i1] = sign;
421 out.set_normal(Unit::new_unchecked(normal));
422 out.set_feature_id(FeatureId::Face(i as u32));
423 }
424 #[cfg(feature = "dim3")]
425 {
426 let i2 = (i1 + 1) % 3;
427 let i3 = (i1 + 2) % 3;
428 let (edge_i2, edge_i3) = if sign > 0.0 {
429 (i2, i3)
430 } else {
431 (i3, i2)
432 };
433 let mask_i2 = !(1 << edge_i2); // The masks are for ensuring each edge has a unique ID.
434 let mask_i3 = !(1 << edge_i3);
435 let mut vertex = self.half_extents;
436 vertex[i1] *= sign;
437
438 let (sbit, msbit) = if sign < 0.0 {
439 (1, 0)
440 } else {
441 (0, 1)
442 };
443 let mut vertex_id = sbit << i1;
444 out.push(Point::from(vertex), FeatureId::Vertex(vertex_id));
445 out.push_edge_feature_id(FeatureId::Edge(
446 edge_i2 as u32 | ((vertex_id & mask_i2) << 2),
447 ));
448
449 vertex[i2] = -sign * self.half_extents[i2];
450 vertex[i3] = sign * self.half_extents[i3];
451 vertex_id |= msbit << i2 | sbit << i3;
452 out.push(Point::from(vertex), FeatureId::Vertex(vertex_id));
453 out.push_edge_feature_id(FeatureId::Edge(
454 edge_i3 as u32 | ((vertex_id & mask_i3) << 2),
455 ));
456
457 vertex[i2] = -self.half_extents[i2];
458 vertex[i3] = -self.half_extents[i3];
459 vertex_id |= 1 << i2 | 1 << i3;
460 out.push(Point::from(vertex), FeatureId::Vertex(vertex_id));
461 out.push_edge_feature_id(FeatureId::Edge(
462 edge_i2 as u32 | ((vertex_id & mask_i2) << 2),
463 ));
464
465 vertex[i2] = sign * self.half_extents[i2];
466 vertex[i3] = -sign * self.half_extents[i3];
467 vertex_id = sbit << i1 | sbit << i2 | msbit << i3;
468 out.push(Point::from(vertex), FeatureId::Vertex(vertex_id));
469 out.push_edge_feature_id(FeatureId::Edge(
470 edge_i3 as u32 | ((vertex_id & mask_i3) << 2),
471 ));
472
473 let mut normal: Vector<Real> = na::zero();
474 normal[i1] = sign;
475 out.set_normal(Unit::new_unchecked(normal));
476
477 if sign > 0.0 {
478 out.set_feature_id(FeatureId::Face(i1 as u32));
479 } else {
480 out.set_feature_id(FeatureId::Face(i1 as u32 + 3));
481 }
482
483 out.recompute_edge_normals();
484 }
485 }
486
487 fn support_face_toward(
488 &self,
489 m: &Isometry<Real>,
490 dir: &Unit<Vector<Real>>,
491 out: &mut ConvexPolygonalFeature,
492 ) {
493 out.clear();
494 let local_dir = m.inverse_transform_vector(dir);
495
496 let mut iamax = 0;
497 let mut amax = local_dir[0].abs();
498
499 // TODO: we should use nalgebra's iamax method.
500 for i in 1..DIM {
501 let candidate = local_dir[i].abs();
502 if candidate > amax {
503 amax = candidate;
504 iamax = i;
505 }
506 }
507
508 if local_dir[iamax] > 0.0 {
509 self.face(FeatureId::Face(iamax as u32), out);
510 out.transform_by(m);
511 } else {
512 self.face(FeatureId::Face((iamax + DIM) as u32), out);
513 out.transform_by(m);
514 }
515 }
516
517 fn support_feature_toward(
518 &self,
519 m: &Isometry<Real>,
520 dir: &Unit<Vector<Real>>,
521 angle: Real,
522 out: &mut ConvexPolygonalFeature,
523 ) {
524 let local_dir = m.inverse_transform_vector(dir);
525 let cang = ComplexField::cos(angle);
526 let mut support_point = self.half_extents;
527
528 out.clear();
529
530 #[cfg(feature = "dim2")]
531 {
532 let mut support_point_id = 0;
533 for i1 in 0..2 {
534 let sign = local_dir[i1].signum();
535 if sign * local_dir[i1] >= cang {
536 if sign > 0.0 {
537 self.face(FeatureId::Face(i1 as u32), out);
538 out.transform_by(m);
539 } else {
540 self.face(FeatureId::Face(i1 as u32 + 2), out);
541 out.transform_by(m);
542 }
543 return;
544 } else {
545 if sign < 0.0 {
546 support_point_id |= 1 << i1;
547 }
548 support_point[i1] *= sign;
549 }
550 }
551
552 // We are not on a face, return the support vertex.
553 out.push(
554 m * Point::from(support_point),
555 FeatureId::Vertex(support_point_id),
556 );
557 out.set_feature_id(FeatureId::Vertex(support_point_id));
558 }
559
560 #[cfg(feature = "dim3")]
561 {
562 let sang = ComplexField::sin(angle);
563 let mut support_point_id = 0;
564
565 // Check faces.
566 for i1 in 0..3 {
567 let sign = local_dir[i1].signum();
568 if sign * local_dir[i1] >= cang {
569 if sign > 0.0 {
570 self.face(FeatureId::Face(i1 as u32), out);
571 out.transform_by(m);
572 } else {
573 self.face(FeatureId::Face(i1 as u32 + 3), out);
574 out.transform_by(m);
575 }
576 return;
577 } else {
578 if sign < 0.0 {
579 support_point[i1] *= sign;
580 support_point_id |= 1 << i1;
581 }
582 }
583 }
584
585 // Check edges.
586 for i in 0..3 {
587 let sign = local_dir[i].signum();
588
589 // sign * local_dir[i] <= cos(pi / 2 - angle)
590 if sign * local_dir[i] <= sang {
591 support_point[i] = -self.half_extents[i];
592 let p1 = Point::from(support_point);
593 support_point[i] = self.half_extents[i];
594 let p2 = Point::from(support_point);
595 let p2_id = support_point_id & !(1 << i);
596 out.push(m * p1, FeatureId::Vertex(support_point_id | (1 << i)));
597 out.push(m * p2, FeatureId::Vertex(p2_id));
598
599 let edge_id = FeatureId::Edge(i as u32 | (p2_id << 2));
600 out.push_edge_feature_id(edge_id);
601 out.set_feature_id(edge_id);
602 return;
603 }
604 }
605
606 // We are not on a face or edge, return the support vertex.
607 out.push(
608 m * Point::from(support_point),
609 FeatureId::Vertex(support_point_id),
610 );
611 out.set_feature_id(FeatureId::Vertex(support_point_id));
612 }
613 }
614
615 fn support_feature_id_toward(&self, local_dir: &Unit<Vector<Real>>) -> FeatureId {
616 let one_degree: Real = na::convert::<f64, Real>(f64::consts::PI / 180.0);
617 let cang = ComplexField::cos(one_degree);
618
619 #[cfg(feature = "dim2")]
620 {
621 let mut support_point_id = 0;
622 for i1 in 0..2 {
623 let sign = local_dir[i1].signum();
624 if sign * local_dir[i1] >= cang {
625 if sign > 0.0 {
626 return FeatureId::Face(i1 as u32);
627 } else {
628 return FeatureId::Face(i1 as u32 + 2);
629 }
630 } else {
631 if sign < 0.0 {
632 support_point_id |= 1 << i1;
633 }
634 }
635 }
636
637 // We are not on a face, return the support vertex.
638 FeatureId::Vertex(support_point_id)
639 }
640
641 #[cfg(feature = "dim3")]
642 {
643 let sang = ComplexField::sin(one_degree);
644 let mut support_point_id = 0;
645
646 // Check faces.
647 for i1 in 0..3 {
648 let sign = local_dir[i1].signum();
649 if sign * local_dir[i1] >= cang {
650 if sign > 0.0 {
651 return FeatureId::Face(i1 as u32);
652 } else {
653 return FeatureId::Face(i1 as u32 + 3);
654 }
655 } else {
656 if sign < 0.0 {
657 support_point_id |= 1 << i1;
658 }
659 }
660 }
661
662 // Check edges.
663 for i in 0..3 {
664 let sign = local_dir[i].signum();
665
666 // sign * local_dir[i] <= cos(pi / 2 - angle)
667 if sign * local_dir[i] <= sang {
668 let mask_i = !(1 << i); // To ensure each edge has a unique id.
669 return FeatureId::Edge(i as u32 | ((support_point_id & mask_i) << 2));
670 }
671 }
672
673 FeatureId::Vertex(support_point_id)
674 }
675 }
676}
677*/