fix(geom): keep finite-scale tolerance and midpoint math stable (#400)
This commit is contained in:
parent
bc2c80289c
commit
95dc3f22a7
|
|
@ -53,7 +53,19 @@ impl Bounds2 {
|
|||
if self.is_empty() { 0.0 } else { self.max.y - self.min.y }
|
||||
}
|
||||
pub fn center(&self) -> Vec2 {
|
||||
if self.is_empty() { Vec2::ZERO } else { self.min.mid(self.max) }
|
||||
if self.is_empty() {
|
||||
return Vec2::ZERO;
|
||||
}
|
||||
// (max - min) can overflow even when both endpoints and their midpoint
|
||||
// are finite. Halve first when the endpoints have opposite signs.
|
||||
let mid = |a: f64, b: f64| {
|
||||
if a.is_sign_negative() != b.is_sign_negative() {
|
||||
a * 0.5 + b * 0.5
|
||||
} else {
|
||||
a + (b - a) * 0.5
|
||||
}
|
||||
};
|
||||
Vec2::new(mid(self.min.x, self.max.x), mid(self.min.y, self.max.y))
|
||||
}
|
||||
pub fn contains(&self, p: Vec2) -> bool {
|
||||
p.x >= self.min.x && p.x <= self.max.x && p.y >= self.min.y && p.y <= self.max.y
|
||||
|
|
@ -75,3 +87,19 @@ impl Bounds2 {
|
|||
[self.min, Vec2::new(self.max.x, self.min.y), self.max, Vec2::new(self.min.x, self.max.y)]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn center_stays_finite_for_opposite_large_coordinates() {
|
||||
let b = Bounds2::new(Vec2::new(-1e308, -1e308), Vec2::new(1e308, 1e308));
|
||||
assert_eq!(b.center(), Vec2::ZERO);
|
||||
let b = Bounds2::new(Vec2::new(1e308, 0.0), Vec2::new(1.6e308, 10.0));
|
||||
assert!(b.center().x.is_finite());
|
||||
assert!((b.center().x / 1e308 - 1.3).abs() < 1e-12);
|
||||
assert_eq!(b.center().y, 5.0);
|
||||
assert_eq!(Bounds2::EMPTY.center(), Vec2::ZERO);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ impl Vec2 {
|
|||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
pub fn near(self, o: Vec2, tol: f64) -> bool {
|
||||
(self - o).len2() <= tol * tol
|
||||
// Squaring very large finite values can overflow on both sides and make
|
||||
// unrelated points compare equal (infinity <= infinity).
|
||||
tol.is_finite() && tol >= 0.0 && self.dist(o) <= tol
|
||||
}
|
||||
pub fn to3(self, z: f64) -> Vec3 {
|
||||
Vec3::new(self.x, self.y, z)
|
||||
|
|
@ -162,7 +164,8 @@ impl Vec3 {
|
|||
Vec3::new(self.y * o.z - self.z * o.y, self.z * o.x - self.x * o.z, self.x * o.y - self.y * o.x)
|
||||
}
|
||||
pub fn len(self) -> f64 {
|
||||
self.dot(self).sqrt()
|
||||
// Avoid overflowing the squared components of large finite vectors.
|
||||
self.x.hypot(self.y).hypot(self.z)
|
||||
}
|
||||
pub fn normalized(self) -> Vec3 {
|
||||
let l = self.len();
|
||||
|
|
@ -197,3 +200,29 @@ impl Neg for Vec3 {
|
|||
Vec3::new(-self.x, -self.y, -self.z)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn near_uses_finite_nonnegative_tolerance_without_squaring() {
|
||||
let origin = Vec2::ZERO;
|
||||
let distant = Vec2::new(1e200, 0.0);
|
||||
assert!(!distant.near(origin, 1e100), "overflowed squares must not compare equal");
|
||||
assert!(distant.near(origin, 1.1e200));
|
||||
assert!(origin.near(origin, 0.0));
|
||||
assert!(!origin.near(origin, -1.0));
|
||||
assert!(!origin.near(origin, f64::INFINITY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_finite_vec3_has_finite_length_and_unit_direction() {
|
||||
let v = Vec3::new(3e200, 4e200, 0.0);
|
||||
assert!((v.len() / 1e200 - 5.0).abs() < 1e-12);
|
||||
let unit = v.normalized();
|
||||
assert!((unit.x - 0.6).abs() < 1e-12);
|
||||
assert!((unit.y - 0.8).abs() < 1e-12);
|
||||
assert!((unit.len() - 1.0).abs() < 1e-12);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue