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*/