Skip to content

Commit 20014b7

Browse files
committed
Revert back to min/max representation of empty AABB
This reverts back to the min/max representation of empty AABB due to regressions introduced by the numerically more tame but not invariant for merging representation. To avoid regressing #161, it also adds a single check to avoid computing the distance to an empty envelope (of the root node of an empty tree). Integer coordinates are always prone to overflow but in the empty case we are forcing this onto the caller whereas every non-empty tree will have AABB that the caller supplied and where we can reasonably ask them to control for overflow or use a custom `Point` impl based on saturating arithmetic.
1 parent 7a48e31 commit 20014b7

File tree

6 files changed

+63
-35
lines changed

6 files changed

+63
-35
lines changed

rstar-benches/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[package]
22
name = "rstar-benches"
3+
edition = "2015"
34
version = "0.1.1"
45
authors = ["Stefan Altmayer <[email protected]>", "The Georust Developers <[email protected]>"]
56

rstar/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Unreleased
2+
3+
## Changed
4+
- Reverted the change to `AABB::new_empty` while still avoiding overflow panics applying selections on empty trees ([PR](https://github.com/georust/rstar/pull/184)
5+
16
# 0.12.1
27

38
## Added

rstar/src/aabb.rs

+27-10
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,12 @@ where
106106
type Point = P;
107107

108108
fn new_empty() -> Self {
109-
new_empty()
109+
let max = P::Scalar::max_value();
110+
let min = P::Scalar::min_value();
111+
Self {
112+
lower: P::from_value(max),
113+
upper: P::from_value(min),
114+
}
110115
}
111116

112117
fn contains_point(&self, point: &P) -> bool {
@@ -219,21 +224,33 @@ where
219224
}
220225
}
221226

222-
fn new_empty<P: Point>() -> AABB<P> {
223-
let one = P::Scalar::one();
224-
let zero = P::Scalar::zero();
225-
AABB {
226-
lower: P::from_value(one),
227-
upper: P::from_value(zero),
228-
}
229-
}
230-
231227
#[cfg(test)]
232228
mod test {
233229
use super::AABB;
234230
use crate::envelope::Envelope;
235231
use crate::object::PointDistance;
236232

233+
#[test]
234+
fn empty_rect() {
235+
let empty = AABB::<[f32; 2]>::new_empty();
236+
237+
let other = AABB::from_corners([1.0, 1.0], [1.0, 1.0]);
238+
let subject = empty.merged(&other);
239+
assert_eq!(other, subject);
240+
241+
let other = AABB::from_corners([0.0, 0.0], [0.0, 0.0]);
242+
let subject = empty.merged(&other);
243+
assert_eq!(other, subject);
244+
245+
let other = AABB::from_corners([0.5, 0.5], [0.5, 0.5]);
246+
let subject = empty.merged(&other);
247+
assert_eq!(other, subject);
248+
249+
let other = AABB::from_corners([-0.5, -0.5], [-0.5, -0.5]);
250+
let subject = empty.merged(&other);
251+
assert_eq!(other, subject);
252+
}
253+
237254
/// Test that min_max_dist_2 is identical to distance_2 for the equivalent
238255
/// min max corner of the AABB. This is necessary to prevent optimizations
239256
/// from inadvertently changing floating point order of operations.

rstar/src/algorithm/iterators.rs

+17-13
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ where
5353
Func: SelectionFunction<T>,
5454
{
5555
pub(crate) fn new(root: &'a ParentNode<T>, func: Func) -> Self {
56-
let current_nodes = if func.should_unpack_parent(&root.envelope()) {
57-
root.children.iter().collect()
58-
} else {
59-
SmallVec::new()
60-
};
56+
let current_nodes =
57+
if !root.children.is_empty() && func.should_unpack_parent(&root.envelope()) {
58+
root.children.iter().collect()
59+
} else {
60+
SmallVec::new()
61+
};
6162

6263
SelectionIterator {
6364
func,
@@ -135,7 +136,7 @@ where
135136
ControlFlow::Continue(())
136137
}
137138

138-
if func.should_unpack_parent(&root.envelope()) {
139+
if !root.children.is_empty() && func.should_unpack_parent(&root.envelope()) {
139140
inner(root, &mut Args { func, visitor })?;
140141
}
141142

@@ -158,11 +159,12 @@ where
158159
Func: SelectionFunction<T>,
159160
{
160161
pub(crate) fn new(root: &'a mut ParentNode<T>, func: Func) -> Self {
161-
let current_nodes = if func.should_unpack_parent(&root.envelope()) {
162-
root.children.iter_mut().collect()
163-
} else {
164-
SmallVec::new()
165-
};
162+
let current_nodes =
163+
if !root.children.is_empty() && func.should_unpack_parent(&root.envelope()) {
164+
root.children.iter_mut().collect()
165+
} else {
166+
SmallVec::new()
167+
};
166168

167169
SelectionIteratorMut {
168170
func,
@@ -240,7 +242,7 @@ where
240242
ControlFlow::Continue(())
241243
}
242244

243-
if func.should_unpack_parent(&root.envelope()) {
245+
if !root.children.is_empty() && func.should_unpack_parent(&root.envelope()) {
244246
inner(root, &mut Args { func, visitor })?;
245247
}
246248

@@ -408,8 +410,10 @@ mod test {
408410

409411
#[test]
410412
fn test_locate_within_distance_on_empty_tree() {
411-
let tree: RTree<[i64; 3]> = RTree::new();
413+
let tree: RTree<[f64; 3]> = RTree::new();
414+
tree.locate_within_distance([0.0, 0.0, 0.0], 10.0);
412415

416+
let tree: RTree<[i64; 3]> = RTree::new();
413417
tree.locate_within_distance([0, 0, 0], 10);
414418
}
415419
}

rstar/src/node.rs

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ where
103103
}
104104

105105
#[cfg(test)]
106+
#[allow(missing_docs)]
106107
pub fn sanity_check<Params>(&self, check_max_size: bool) -> Option<usize>
107108
where
108109
Params: RTreeParams,

rstar/src/point.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,16 @@ use num_traits::{Bounded, Num, Signed, Zero};
1414
/// # Example
1515
/// ```
1616
/// # extern crate num_traits;
17-
/// use num_traits::{Num, One, Signed, Zero};
17+
/// use num_traits::{Bounded, Num, Signed};
1818
///
1919
/// #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
2020
/// struct MyFancyNumberType(f32);
2121
///
22-
/// impl Zero for MyFancyNumberType {
22+
/// impl num_traits::Bounded for MyFancyNumberType {
2323
/// // ... details hidden ...
24-
/// # fn zero() -> Self { MyFancyNumberType(Zero::zero()) }
25-
/// # fn is_zero(&self) -> bool { unimplemented!() }
26-
/// }
27-
///
28-
/// impl One for MyFancyNumberType {
29-
/// // ... details hidden ...
30-
/// # fn one() -> Self { MyFancyNumberType(One::one()) }
24+
/// # fn min_value() -> Self { Self(Bounded::min_value()) }
25+
/// #
26+
/// # fn max_value() -> Self { Self(Bounded::max_value()) }
3127
/// }
3228
///
3329
/// impl Signed for MyFancyNumberType {
@@ -58,9 +54,13 @@ use num_traits::{Bounded, Num, Signed, Zero};
5854
/// rtree.insert([MyFancyNumberType(0.0), MyFancyNumberType(0.0)]);
5955
/// # }
6056
///
61-
/// # impl num_traits::Bounded for MyFancyNumberType {
62-
/// # fn min_value() -> Self { unimplemented!() }
63-
/// # fn max_value() -> Self { unimplemented!() }
57+
/// # impl num_traits::Zero for MyFancyNumberType {
58+
/// # fn zero() -> Self { unimplemented!() }
59+
/// # fn is_zero(&self) -> bool { unimplemented!() }
60+
/// # }
61+
/// #
62+
/// # impl num_traits::One for MyFancyNumberType {
63+
/// # fn one() -> Self { unimplemented!() }
6464
/// # }
6565
/// #
6666
/// # impl core::ops::Mul for MyFancyNumberType {

0 commit comments

Comments
 (0)