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