ncollide3d/bounding_volume/
circular_cone.rs

1use crate::math::Vector;
2use na::{self, RealField, Unit};
3
4/// A cone with a circular basis and its apex at the origin.
5///
6/// A circular cone is a set of half-lines emanating from its apex and forming an angle of at most `angle` with its `axis`.
7/// It is usually used to bound a set of directions like normals and tangents.
8/// It is convex and have a circular basis.
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub enum CircularCone<N: RealField + Copy> {
12    /// A cone which is the whole space.
13    Full,
14    /// An empty cone containing only the zero vector.
15    Empty,
16    /// All the vectors emanating from the origin, with a maximal `angle` wrt the given `axis`.
17    Spread {
18        /// The cone axis.
19        axis: Unit<Vector<N>>,
20        /// Half of the cone apex angle, i.e., the largest angle possible between the axis and a vector contained by this cone.
21        angle: N,
22    },
23}
24
25// FIXME: rewrite all those without calls to acos()
26// (by performing tests on the cos themselves instead of the actual angles).
27
28impl<N: RealField + Copy> CircularCone<N> {
29    /// Creates a circular cone from a set of vectors.
30    pub fn from_vectors(dirs: &[Unit<Vector<N>>]) -> Self {
31        let mut res = CircularCone::Empty;
32
33        for dir in dirs {
34            res.push(*dir)
35        }
36
37        res
38    }
39
40    /// Returns `true` if this cone is empty.
41    pub fn is_empty(&self) -> bool {
42        *self == CircularCone::Empty
43    }
44
45    /// Enlarge this cone so it contains `dir` too.
46    pub fn push(&mut self, dir: Unit<Vector<N>>) {
47        match *self {
48            CircularCone::Full => {}
49            CircularCone::Empty => {
50                *self = CircularCone::Spread {
51                    axis: dir,
52                    angle: N::zero(),
53                }
54            }
55            CircularCone::Spread {
56                ref mut axis,
57                ref mut angle,
58            } => {
59                let dot = axis.dot(&dir);
60                let delta_ang = dot.acos();
61
62                if delta_ang <= *angle {
63                    // The current cone already contains dir.
64                } else {
65                    let ortho = *dir - **axis * dot;
66                    if let Some(basis2) = Unit::try_new(ortho, N::zero()) {
67                        let hang = delta_ang * na::convert(0.5);
68                        let (s, c) = hang.sin_cos();
69                        *axis = Unit::new_unchecked(**axis * c + *basis2 * s);
70                        *angle = hang + *angle * na::convert(0.5);
71                    }
72                    // Otherwise, dir and axis are collinear so there is nothing more to do.
73                }
74            }
75        }
76    }
77
78    /// Returns `true` if this cone intersects `other`.
79    pub fn intersects(&self, other: &Self) -> bool {
80        match (self, other) {
81            (CircularCone::Empty, _) => false,
82            (_, CircularCone::Empty) => false,
83            (CircularCone::Full, _) => true,
84            (_, CircularCone::Full) => true,
85            (
86                CircularCone::Spread {
87                    axis: axis1,
88                    angle: angle1,
89                },
90                CircularCone::Spread {
91                    axis: axis2,
92                    angle: angle2,
93                },
94            ) => {
95                let ang = axis1.dot(&axis2).acos();
96                ang <= *angle1 + *angle2
97            }
98        }
99    }
100
101    /// Tests if this circular cone, extended to be a double cone, intersects the `other` circular cone, also seen as a double cone.
102    pub fn double_cones_intersect(&self, other: &Self) -> bool {
103        match (self, other) {
104            (CircularCone::Empty, _) => false,
105            (_, CircularCone::Empty) => false,
106            (CircularCone::Full, _) => true,
107            (_, CircularCone::Full) => true,
108            (
109                CircularCone::Spread {
110                    axis: axis1,
111                    angle: angle1,
112                },
113                CircularCone::Spread {
114                    axis: axis2,
115                    angle: angle2,
116                },
117            ) => {
118                let ang = axis1.dot(&axis2).acos();
119                ang <= *angle1 + *angle2 || (N::pi() - ang) <= *angle1 + *angle2
120            }
121        }
122    }
123
124    /// Returns `true` if this cone contains `other`.
125    pub fn contains(&self, other: &Self) -> bool {
126        match (self, other) {
127            (CircularCone::Empty, _) => false,
128            (CircularCone::Full, _) => *other != CircularCone::Empty,
129            (_, CircularCone::Full) => false,
130            (_, CircularCone::Empty) => true,
131            (
132                CircularCone::Spread {
133                    axis: axis1,
134                    angle: angle1,
135                },
136                CircularCone::Spread {
137                    axis: axis2,
138                    angle: angle2,
139                },
140            ) => {
141                let ang = axis1.dot(&axis2).acos();
142                ang + *angle2 <= *angle1
143            }
144        }
145    }
146
147    /// Merges this cone with `other` in-place.
148    pub fn merge(&mut self, other: &Self) {
149        *self = self.merged(other)
150    }
151
152    /// Merges this cone with `other`.
153    pub fn merged(&self, other: &Self) -> Self {
154        match (self, other) {
155            (CircularCone::Empty, _) => *other,
156            (CircularCone::Full, _) => CircularCone::Full,
157            (_, CircularCone::Empty) => *self,
158            (_, CircularCone::Full) => CircularCone::Full,
159            (
160                CircularCone::Spread {
161                    axis: axis1,
162                    angle: angle1,
163                },
164                CircularCone::Spread {
165                    axis: axis2,
166                    angle: angle2,
167                },
168            ) => {
169                let dot = axis1.dot(&axis2);
170                let ang = dot.acos();
171                if ang + *angle1 <= *angle2 {
172                    // self is contained in other
173                    // so there is nothing to do for the merge.
174                    *self
175                } else if ang + *angle2 <= *angle1 {
176                    // other is contained in self
177                    *other
178                } else {
179                    let ortho = **axis2 - **axis1 * dot;
180
181                    if let Some(basis2) = Unit::try_new(ortho, N::zero()) {
182                        let partial_sum = (ang + *angle2) * na::convert(0.5);
183                        let (s, c) = partial_sum.sin_cos();
184                        let new_axis = **axis1 * c + *basis2 * s;
185                        CircularCone::Spread {
186                            axis: Unit::new_unchecked(new_axis),
187                            angle: partial_sum + *angle1 * na::convert(0.5),
188                        }
189                    } else {
190                        // This should be unreachable because that means both axii are superimposed so one
191                        // of the first `if` statements above should have kicked in already.
192                        // But this might happen due to rounding errors. Just return the
193                        // cone with the largest angle.
194                        if *angle2 > *angle1 {
195                            *other
196                        } else {
197                            *self
198                        }
199                    }
200                }
201            }
202        }
203    }
204}
205
206///// Checks if the unit vector `dir` is inside of the circular cone described by the given `axis` and apex half-angle `angle`.
207//pub fn cone_contains_dir<N: RealField + Copy>(axis: &Unit<Vector<N>>, angle: N, dir: &Unit<Vector<N>>) -> bool {
208//    let ang = axis.dot(dir).acos();
209//    ang <= angle
210//}
211//
212///// Checks if the unit vector `dir` is inside of the polar of the circular cone described by the given `axis` and apex half-angle `angle`.
213//pub fn cone_polar_contains_dir<N: RealField + Copy>(
214//    axis: &Unit<Vector<N>>,
215//    angle: N,
216//    dir: &Unit<Vector<N>>,
217//) -> bool
218//{
219//    let ang = axis.dot(dir).acos();
220//    ang >= angle + N::frac_pi_2()
221//}