Skip to content

Commit 31d4f4b

Browse files
committed
AABB::furthest_point_to
1 parent c3dadf8 commit 31d4f4b

3 files changed

Lines changed: 166 additions & 4 deletions

File tree

fenris-geometry/src/lib.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ where
210210
}
211211

212212
pub fn contains_point(&self, point: &OPoint<T, D>) -> bool {
213-
(0..D::dim()).all(|dim| point[dim] > self.min[dim] && point[dim] < self.max[dim])
213+
(0..D::dim()).all(|dim| point[dim] >= self.min[dim] && point[dim] <= self.max[dim])
214214
}
215215

216216
pub fn intersects(&self, other: &Self) -> bool {
@@ -257,6 +257,63 @@ where
257257
}).into()
258258
})
259259
}
260+
261+
/// Compute the point in the bounding box furthest away from the given point.
262+
///
263+
/// # Panics
264+
///
265+
/// Panics if two distances cannot be ordered. This typically only happens if
266+
/// one of the numbers is not a number (NaN) or the comparison is not sensible, such as
267+
/// comparing two infinities. Since given finite coordinates no distance should be infinite,
268+
/// this method will realistically only panic in cases where one of the points
269+
/// --- either of the bounding box or the query point --- has components that are not
270+
/// finite numbers.
271+
#[replace_float_literals(T::from_f64(literal).unwrap())]
272+
pub fn furthest_point_to(&self, point: &OPoint<T, D>) -> OPoint<T, D>
273+
where
274+
DefaultAllocator: Allocator<usize, D>
275+
{
276+
// It turns out that we can choose, along each dimension, the point in the interval
277+
// [a_i, b_i] furthest away from p_i.
278+
point.coords.zip_zip_map(
279+
&self.min.coords,
280+
&self.max.coords,
281+
|p_i, a_i, b_i| {
282+
let mid = (a_i + b_i) / 2.0;
283+
if p_i < mid {
284+
b_i
285+
} else {
286+
a_i
287+
}
288+
}
289+
).into()
290+
}
291+
292+
/// The squared distance to the point in the bounding box furthest away from the given point.
293+
///
294+
/// # Panics
295+
///
296+
/// Panic behavior is identical to [`furthest_point_to`](Self::furthest_point_to).
297+
pub fn max_dist2_to(&self, point: &OPoint<T, D>) -> T
298+
where
299+
// TODO: Use DimAllocator and SmallDim
300+
DefaultAllocator: Allocator<usize, D>
301+
{
302+
(self.furthest_point_to(point) - point).norm_squared()
303+
}
304+
305+
/// The distance to the point in the bounding box furthest away from the given point.
306+
///
307+
/// # Panics
308+
///
309+
/// Panic behavior is identical to [`max_dist2_to`](Self::max_dist2_to).
310+
pub fn max_dist_to(&self, point: &OPoint<T, D>) -> T
311+
where
312+
// TODO: Use DimAllocator and SmallDim
313+
DefaultAllocator: Allocator<usize, D>
314+
{
315+
self.max_dist2_to(point).sqrt()
316+
}
260317
}
261318

262319
fn intervals_intersect<T: Real>([l1, u1]: [T; 2], [l2, u2]: [T; 2]) -> bool {

fenris-geometry/src/proptest.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
1-
// use crate::procedural::create_rectangular_uniform_quad_mesh_2d;
21
use crate::Orientation::Counterclockwise;
3-
use crate::{HalfPlane, LineSegment2d, Orientation, Quad2d, Triangle, Triangle2d, Triangle3d};
2+
use crate::{AxisAlignedBoundingBox, AxisAlignedBoundingBox2d, AxisAlignedBoundingBox3d, HalfPlane, LineSegment2d, Orientation, Quad2d, Triangle, Triangle2d, Triangle3d};
43
use nalgebra::proptest::vector;
5-
use nalgebra::{DimName, Point2, Point3, Unit, Vector2, Vector3, U2, U3};
4+
use nalgebra::{DimName, Point2, Point3, Unit, Vector2, Vector3, U2, U3, DefaultAllocator, RealField};
5+
use nalgebra::allocator::Allocator;
66
use proptest::prelude::*;
77

8+
pub fn aabb<D>() -> impl Strategy<Value=AxisAlignedBoundingBox<f64, D>>
9+
where
10+
D: DimName,
11+
DefaultAllocator: Allocator<f64, D>
12+
{
13+
let value_strategy = -10.0 .. 10.0;
14+
(vector(value_strategy.clone(), D::name()), vector(value_strategy, D::name()))
15+
.prop_map(|(a, b)| {
16+
let min = a.zip_map(&b, |a_i, b_i| RealField::min(a_i, b_i));
17+
let max = a.zip_map(&b, |a_i, b_i| RealField::max(a_i, b_i));
18+
AxisAlignedBoundingBox::new(min.into(), max.into())
19+
})
20+
}
21+
22+
pub fn aabb2() -> impl Strategy<Value=AxisAlignedBoundingBox2d<f64>> {
23+
aabb()
24+
}
25+
26+
pub fn aabb3() -> impl Strategy<Value=AxisAlignedBoundingBox3d<f64>> {
27+
aabb()
28+
}
29+
830
pub fn vector2() -> impl Strategy<Value = Vector2<f64>> {
931
vector(-10.0..10.0, U2::name())
1032
}

fenris-geometry/tests/unit_tests/aabb.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
use matrixcompare::assert_scalar_eq;
12
use fenris_geometry::AxisAlignedBoundingBox;
23
use nalgebra::{DefaultAllocator, DimName, OPoint, point, U2};
34
use nalgebra::allocator::Allocator;
5+
use proptest::prelude::*;
46
use fenris::allocators::DimAllocator;
7+
use fenris_geometry::proptest::{aabb2, aabb3, point2, point3};
8+
use nalgebra::distance_squared;
59

610
#[test]
711
fn aabb_intersects_2d() {
@@ -110,6 +114,85 @@ fn test_aabb_corners_iter() {
110114
].map(OPoint::from);
111115
assert_unordered_eq!(corners(&aabb), expected);
112116
}
117+
}
118+
119+
#[test]
120+
fn test_furthest_point_2d() {
121+
let aabb = AxisAlignedBoundingBox::new(point![1.0, 1.0], point![2.0, 3.0]);
122+
123+
{
124+
let p = point![0.0, 0.0];
125+
let q = aabb.furthest_point_to(&p);
126+
assert_eq!(q, point![2.0, 3.0]);
127+
// We check all the convenience method results here just to have a unit test that does
128+
// this, but the consistency is separately tested by proptests, which is why
129+
// we don't do this for every test
130+
let dist2: f64 = distance_squared(&q, &p);
131+
assert_scalar_eq!(dist2, 13.0);
132+
assert_scalar_eq!(dist2, aabb.max_dist2_to(&p));
133+
assert_scalar_eq!(aabb.max_dist_to(&p), f64::sqrt(13.0));
134+
}
135+
136+
{
137+
let p = point![1.5, 2.0];
138+
let q = aabb.furthest_point_to(&p);
139+
// The exact point is not unique: any corner will be applicable
140+
assert_scalar_eq!(distance_squared(&q, &p), 1.25);
141+
}
142+
}
143+
144+
proptest! {
113145

146+
#[test]
147+
fn aabb_max_dists_agree_with_furthest_point_2d(point in point2(), aabb in aabb2()) {
148+
let q = aabb.furthest_point_to(&point);
149+
let dist2 = distance_squared(&q, &point);
150+
prop_assert_eq!(aabb.max_dist2_to(&point), dist2);
151+
prop_assert_eq!(aabb.max_dist_to(&point), dist2.sqrt());
152+
}
153+
154+
#[test]
155+
fn aabb_max_dists_agree_with_furthest_point_3d(point in point3(), aabb in aabb3()) {
156+
let q = aabb.furthest_point_to(&point);
157+
let dist2 = distance_squared(&q, &point);
158+
prop_assert_eq!(aabb.max_dist2_to(&point), dist2);
159+
prop_assert_eq!(aabb.max_dist_to(&point), dist2.sqrt());
160+
}
161+
162+
#[test]
163+
fn aabb_furthest_point_2d(p in point2(), aabb in aabb2()) {
164+
// The furthest point in the AABB is *always* a corner, so we must satisfy
165+
// dist(p, q) <= dist(p, c)
166+
// for all corners c and furthest point q
167+
let q = aabb.furthest_point_to(&p);
168+
let further_away_than_all_corners = aabb.corners_iter()
169+
.all(|corner| distance_squared(&p, &q) >= distance_squared(&p, &corner));
170+
prop_assert!(further_away_than_all_corners);
171+
172+
// The result should be exactly one of the corners, and since there are no floating
173+
// point operations applied to the result (all numbers are just copied),
174+
// there should also be no round-off error in the result, so we should
175+
// be safe to check if the point is contained in the AABB, despite the fact that it
176+
// resides exactly on the boundary!
177+
prop_assert!(aabb.contains_point(&q));
178+
}
179+
180+
#[test]
181+
fn aabb_furthest_point_3d(p in point3(), aabb in aabb3()) {
182+
// The furthest point in the AABB is *always* a corner, so we must satisfy
183+
// dist(p, q) <= dist(p, c)
184+
// for all corners c and furthest point q
185+
let q = aabb.furthest_point_to(&p);
186+
let further_away_than_all_corners = aabb.corners_iter()
187+
.all(|corner| distance_squared(&p, &q) >= distance_squared(&p, &corner));
188+
prop_assert!(further_away_than_all_corners);
189+
190+
// The result should be exactly one of the corners, and since there are no floating
191+
// point operations applied to the result (all numbers are just copied),
192+
// there should also be no round-off error in the result, so we should
193+
// be safe to check if the point is contained in the AABB, despite the fact that it
194+
// resides exactly on the boundary!
195+
prop_assert!(aabb.contains_point(&q));
196+
}
114197

115198
}

0 commit comments

Comments
 (0)