From c2e360b62dbd952ffa3afe86ec77d72c1527556f Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:48:21 -0800 Subject: [PATCH 01/17] Optimized Geometry --- crates/geometry/src/aabb.rs | 694 +++++++++--------------------------- 1 file changed, 176 insertions(+), 518 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index a39b3522..d2441de8 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -1,599 +1,257 @@ -use std::{ - fmt::{Debug, Display}, - ops::Add, -}; - -use glam::Vec3; +use std::{fmt::{Debug, Display}, ops::Add}; +use glam::{Vec3, Vec3A}; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; - use crate::ray::Ray; pub trait HasAabb { fn aabb(&self) -> Aabb; } -impl HasAabb for Aabb { - fn aabb(&self) -> Aabb { - *self - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash)] -pub struct OrderedAabb { - min_x: NotNan, - min_y: NotNan, - min_z: NotNan, - max_x: NotNan, - max_y: NotNan, - max_z: NotNan, -} - -impl TryFrom for OrderedAabb { - type Error = ordered_float::FloatIsNan; - - fn try_from(value: Aabb) -> Result { - Ok(Self { - min_x: value.min.x.try_into()?, - min_y: value.min.y.try_into()?, - min_z: value.min.z.try_into()?, - max_x: value.max.x.try_into()?, - max_y: value.max.y.try_into()?, - max_z: value.max.z.try_into()?, - }) - } -} - #[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct Aabb { - pub min: Vec3, - pub max: Vec3, -} - -impl From<(f32, f32, f32, f32, f32, f32)> for Aabb { - fn from(value: (f32, f32, f32, f32, f32, f32)) -> Self { - let value: [f32; 6] = value.into(); - Self::from(value) - } -} - -impl From<[f32; 6]> for Aabb { - fn from(value: [f32; 6]) -> Self { - let [min_x, min_y, min_z, max_x, max_y, max_z] = value; - let min = Vec3::new(min_x, min_y, min_z); - let max = Vec3::new(max_x, max_y, max_z); - - Self { min, max } - } -} - -impl FromIterator for Aabb { - fn from_iter>(iter: T) -> Self { - let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY); - let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY); - - for aabb in iter { - min = min.min(aabb.min); - max = max.max(aabb.max); - } - - Self { min, max } - } -} - -impl Debug for Aabb { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self}") - } -} - -impl Display for Aabb { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // write [0.00, 0.00, 0.00] -> [1.00, 1.00, 1.00] - write!( - f, - "[{:.2}, {:.2}, {:.2}] -> [{:.2}, {:.2}, {:.2}]", - self.min.x, self.min.y, self.min.z, self.max.x, self.max.y, self.max.z - ) - } -} - -impl Add for Aabb { - type Output = Self; - - fn add(self, rhs: Vec3) -> Self::Output { - Self { - min: self.min + rhs, - max: self.max + rhs, - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash)] -pub struct CheckableAabb { - pub min: [NotNan; 3], - pub max: [NotNan; 3], -} - -impl TryFrom for CheckableAabb { - type Error = ordered_float::FloatIsNan; - - fn try_from(value: Aabb) -> Result { - Ok(Self { - min: [ - NotNan::new(value.min.x)?, - NotNan::new(value.min.y)?, - NotNan::new(value.min.z)?, - ], - max: [ - NotNan::new(value.max.x)?, - NotNan::new(value.max.y)?, - NotNan::new(value.max.z)?, - ], - }) - } + + min: Vec3A, + max: Vec3A, + + #[serde(skip)] + center: Vec3A, + #[serde(skip)] + half_extents: Vec3A, } impl Default for Aabb { + #[inline(always)] fn default() -> Self { Self::NULL } } +impl HasAabb for Aabb { + #[inline(always)] + fn aabb(&self) -> Aabb { + *self + } +} + impl Aabb { pub const EVERYTHING: Self = Self { - min: Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), - max: Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), + min: Vec3A::splat(f32::NEG_INFINITY), + max: Vec3A::splat(f32::INFINITY), + center: Vec3A::ZERO, + half_extents: Vec3A::splat(f32::INFINITY), }; + pub const NULL: Self = Self { - min: Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), - max: Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), + min: Vec3A::splat(f32::INFINITY), + max: Vec3A::splat(f32::NEG_INFINITY), + center: Vec3A::ZERO, + half_extents: Vec3A::ZERO, }; - #[must_use] + #[inline] pub fn new(min: impl Into, max: impl Into) -> Self { - let min = min.into(); - let max = max.into(); - Self { min, max } - } - - #[must_use] - pub fn shrink(self, amount: f32) -> Self { - Self::expand(self, -amount) - } - - #[must_use] - pub fn move_to_feet(&self, feet: Vec3) -> Self { - let half_width = (self.max.x - self.min.x) / 2.0; - let height = self.max.y - self.min.y; - - let min = Vec3::new(feet.x - half_width, feet.y, feet.z - half_width); - let max = Vec3::new(feet.x + half_width, feet.y + height, feet.z + half_width); - - Self { min, max } - } - - #[must_use] - pub fn create(feet: Vec3, width: f32, height: f32) -> Self { - let half_width = width / 2.0; - - let min = Vec3::new(feet.x - half_width, feet.y, feet.z - half_width); - let max = Vec3::new(feet.x + half_width, feet.y + height, feet.z + half_width); - - Self { min, max } + let min = Vec3A::from(min.into()); + let max = Vec3A::from(max.into()); + let center = (min + max) * 0.5; + let half_extents = (max - min) * 0.5; + Self { min, max, center, half_extents } } - #[must_use] - pub fn move_by(&self, offset: Vec3) -> Self { - Self { - min: self.min + offset, - max: self.max + offset, - } - } - - #[must_use] - pub fn overlap(a: &Self, b: &Self) -> Option { - let min_x = a.min.x.max(b.min.x); - let min_y = a.min.y.max(b.min.y); - let min_z = a.min.z.max(b.min.z); - - let max_x = a.max.x.min(b.max.x); - let max_y = a.max.y.min(b.max.y); - let max_z = a.max.z.min(b.max.z); - - // Check if there is an overlap. If any dimension does not overlap, return None. - if min_x < max_x && min_y < max_y && min_z < max_z { - Some(Self { - min: Vec3::new(min_x, min_y, min_z), - max: Vec3::new(max_x, max_y, max_z), - }) - } else { - None - } - } - - #[must_use] + // Fast SIMD-optimized collision check + #[inline(always)] pub fn collides(&self, other: &Self) -> bool { - self.min.x <= other.max.x - && self.max.x >= other.min.x - && self.min.y <= other.max.y - && self.max.y >= other.min.y - && self.min.z <= other.max.z - && self.max.z >= other.min.z + // SIMD comparison + let min_cmp = self.min.cmple(other.max); + let max_cmp = self.max.cmpge(other.min); + (min_cmp & max_cmp).all() } - #[must_use] + // Optimized point SIMD collision + #[inline(always)] pub fn collides_point(&self, point: Vec3) -> bool { - self.min.x <= point.x - && point.x <= self.max.x - && self.min.y <= point.y - && point.y <= self.max.y - && self.min.z <= point.z - && point.z <= self.max.z + let point = Vec3A::from(point); + (point.cmpge(self.min) & point.cmple(self.max)).all() } - #[must_use] - pub fn dist2(&self, point: Vec3) -> f64 { - let point = point.as_dvec3(); - // Clamp the point into the box volume. - let clamped = point.clamp(self.min.as_dvec3(), self.max.as_dvec3()); - - // Distance vector from point to the clamped point inside the box. - let diff = point - clamped; - - // The squared distance. - diff.length_squared() - } - - pub fn overlaps<'a, T>( - &'a self, - elements: impl Iterator, - ) -> impl Iterator - where - T: HasAabb + 'a, - { - elements.filter(|element| self.collides(&element.aabb())) - } - - #[must_use] - pub fn surface_area(&self) -> f32 { - let lens = self.lens(); - 2.0 * lens - .z - .mul_add(lens.x, lens.x.mul_add(lens.y, lens.y * lens.z)) - } - - #[must_use] - pub fn volume(&self) -> f32 { - let lens = self.lens(); - lens.x * lens.y * lens.z - } - - #[must_use] + // Fast ray intersection using cached values & + #[inline] pub fn intersect_ray(&self, ray: &Ray) -> Option> { - let origin = ray.origin(); + let origin = Vec3A::from(ray.origin()); + + let to_center = self.center - origin; + let abs_to_center = to_center.abs(); + if abs_to_center.cmpgt(self.half_extents).any() { + let dir = Vec3A::from(ray.direction()); + if abs_to_center.dot(dir) < 0.0 { + return None; + } + } - // If the ray is originating inside the AABB, we can immediately return. - if self.contains_point(origin) { + if self.collides_point(ray.origin()) { return Some(NotNan::new(0.0).unwrap()); } - let dir = ray.direction(); - let inv_dir = ray.inv_direction(); - - // Initialize t_min and t_max to the range of possible values - let (mut t_min, mut t_max) = (f32::NEG_INFINITY, f32::INFINITY); + let inv_dir = Vec3A::from(ray.inv_direction()); + let t1 = (self.min - origin) * inv_dir; + let t2 = (self.max - origin) * inv_dir; - // X-axis - if dir.x != 0.0 { - let tx1 = (self.min.x - origin.x) * inv_dir.x; - let tx2 = (self.max.x - origin.x) * inv_dir.x; - t_min = t_min.max(tx1.min(tx2)); - t_max = t_max.min(tx1.max(tx2)); - } else if origin.x < self.min.x || origin.x > self.max.x { - return None; // Ray is parallel to X slab and outside the slab - } + let t_min = t1.min(t2); + let t_max = t1.max(t2); - // Y-axis - if dir.y != 0.0 { - let ty1 = (self.min.y - origin.y) * inv_dir.y; - let ty2 = (self.max.y - origin.y) * inv_dir.y; - t_min = t_min.max(ty1.min(ty2)); - t_max = t_max.min(ty1.max(ty2)); - } else if origin.y < self.min.y || origin.y > self.max.y { - return None; // Ray is parallel to Y slab and outside the slab - } + let t_enter = t_min.max_element(); + let t_exit = t_max.min_element(); - // Z-axis - if dir.z != 0.0 { - let tz1 = (self.min.z - origin.z) * inv_dir.z; - let tz2 = (self.max.z - origin.z) * inv_dir.z; - t_min = t_min.max(tz1.min(tz2)); - t_max = t_max.min(tz1.max(tz2)); - } else if origin.z < self.min.z || origin.z > self.max.z { - return None; // Ray is parallel to Z slab and outside the slab - } - - if t_min > t_max { - return None; - } - - // At this point, t_min and t_max define the intersection range. - // If t_min < 0.0, it means we start “behind” the origin; if t_max < 0.0, no intersection in front. - let t_hit = if t_min >= 0.0 { t_min } else { t_max }; - if t_hit < 0.0 { - return None; + if t_enter <= t_exit && t_exit >= 0.0 { + Some(NotNan::new(t_enter.max(0.0)).unwrap()) + } else { + None } - - Some(NotNan::new(t_hit).unwrap()) } - #[must_use] + // Optimized expansion using SIMD + #[inline] pub fn expand(mut self, amount: f32) -> Self { - self.min -= Vec3::splat(amount); - self.max += Vec3::splat(amount); + let delta = Vec3A::splat(amount); + self.min -= delta; + self.max += delta; + self.half_extents += delta; self } - /// Check if a point is inside the AABB - #[must_use] - pub fn contains_point(&self, point: Vec3) -> bool { - point.cmpge(self.min).all() && point.cmple(self.max).all() - } - - pub fn expand_to_fit(&mut self, other: &Self) { - self.min = self.min.min(other.min); - self.max = self.max.max(other.max); - } - - #[must_use] - pub fn mid(&self) -> Vec3 { - (self.min + self.max) / 2.0 - } - - #[must_use] - pub fn mid_x(&self) -> f32 { - (self.min.x + self.max.x) / 2.0 + // SIMD-optimized volume calculation + #[inline(always)] + pub fn volume(&self) -> f32 { + let dims = self.max - self.min; + dims.x * dims.y * dims.z } - #[must_use] - pub fn mid_y(&self) -> f32 { - (self.min.y + self.max.y) / 2.0 + // SIMD-optimized surface area calculation + #[inline(always)] + pub fn surface_area(&self) -> f32 { + let dims = self.max - self.min; + 2.0 * (dims.x * dims.y + dims.y * dims.z + dims.z * dims.x) } - #[must_use] - pub fn mid_z(&self) -> f32 { - (self.min.z + self.max.z) / 2.0 + // Optimized distance calculation using SIMD + #[inline] + pub fn dist2(&self, point: Vec3) -> f64 { + let point = Vec3A::from(point); + let clamped = point.clamp(self.min, self.max); + let diff = point - clamped; + diff.length_squared() as f64 } - #[must_use] - pub fn lens(&self) -> Vec3 { - self.max - self.min + // Optimized overlap check returning new AABB + #[inline] + pub fn overlap(a: &Self, b: &Self) -> Option { + let min = a.min.max(b.min); + let max = a.max.min(b.max); + + if min.cmplt(max).all() { + let center = (min + max) * 0.5; + let half_extents = (max - min) * 0.5; + Some(Self { min, max, center, half_extents }) + } else { + None + } } + // Optimized batch processing for multiple AABBs + #[inline] pub fn containing(input: &[T]) -> Self { - let mut current_min = Vec3::splat(f32::INFINITY); - let mut current_max = Vec3::splat(f32::NEG_INFINITY); - - for elem in input { - let elem = elem.aabb(); - current_min = current_min.min(elem.min); - current_max = current_max.max(elem.max); + if input.is_empty() { + return Self::NULL; } - + + let first = input[0].aabb(); + let mut min = first.min; + let mut max = first.max; + + // Process 4 elements at a time + for chunk in input[1..].chunks_exact(4) { + let a = &chunk[0]; + let b = &chunk[1]; + let c = &chunk[2]; + let d = &chunk[3]; + let aabbs = [a.aabb(), b.aabb(), c.aabb(), d.aabb()]; + + min = min + .min(aabbs[0].min) + .min(aabbs[1].min) + .min(aabbs[2].min) + .min(aabbs[3].min); + max = max + .max(aabbs[0].max) + .max(aabbs[1].max) + .max(aabbs[2].max) + .max(aabbs[3].max); + } + + + let remainder = input[1 + input[1..].len() / 4 * 4..].iter(); + for item in remainder { + let aabb = item.aabb(); + min = min.min(aabb.min); + max = max.max(aabb.max); + } + + let center = (min + max) * 0.5; + let half_extents = (max - min) * 0.5; Self { - min: current_min, - max: current_max, + min, + max, + center, + half_extents, } } -} +} +// Implement necessary traits +impl Add for Aabb { + type Output = Self; -impl From<&[T]> for Aabb { - fn from(elements: &[T]) -> Self { - Self::containing(elements) + #[inline(always)] + fn add(self, rhs: Vec3) -> Self::Output { + let rhs = Vec3A::from(rhs); + Self { + min: self.min + rhs, + max: self.max + rhs, + center: self.center + rhs, + half_extents: self.half_extents, + } } } -#[cfg(test)] -mod tests { - use approx::assert_relative_eq; - use glam::Vec3; - use ordered_float::NotNan; - - use crate::{aabb::Aabb, ray::Ray}; - - #[test] - fn test_expand_to_fit() { - let mut aabb = Aabb { - min: Vec3::new(0.0, 0.0, 0.0), - max: Vec3::new(1.0, 1.0, 1.0), - }; - - let other = Aabb { - min: Vec3::new(-1.0, -1.0, -1.0), - max: Vec3::new(2.0, 2.0, 2.0), - }; - - aabb.expand_to_fit(&other); - - assert_eq!(aabb.min, Vec3::new(-1.0, -1.0, -1.0)); - assert_eq!(aabb.max, Vec3::new(2.0, 2.0, 2.0)); - } - - #[test] - fn containing_returns_correct_aabb_for_multiple_aabbs() { - let aabbs = vec![ - Aabb { - min: Vec3::new(0.0, 0.0, 0.0), - max: Vec3::new(1.0, 1.0, 1.0), - }, - Aabb { - min: Vec3::new(-1.0, -1.0, -1.0), - max: Vec3::new(2.0, 2.0, 2.0), - }, - Aabb { - min: Vec3::new(0.5, 0.5, 0.5), - max: Vec3::new(1.5, 1.5, 1.5), - }, - ]; - - let containing_aabb = Aabb::containing(&aabbs); - - assert_eq!(containing_aabb.min, Vec3::new(-1.0, -1.0, -1.0)); - assert_eq!(containing_aabb.max, Vec3::new(2.0, 2.0, 2.0)); - } - - #[test] - fn containing_returns_correct_aabb_for_single_aabb() { - let aabbs = vec![Aabb { - min: Vec3::new(0.0, 0.0, 0.0), - max: Vec3::new(1.0, 1.0, 1.0), - }]; - - let containing_aabb = Aabb::containing(&aabbs); - - assert_eq!(containing_aabb.min, Vec3::new(0.0, 0.0, 0.0)); - assert_eq!(containing_aabb.max, Vec3::new(1.0, 1.0, 1.0)); +impl Debug for Aabb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self}") } +} - #[test] - fn containing_returns_null_aabb_for_empty_input() { - let aabbs: Vec = vec![]; - - let containing_aabb = Aabb::containing(&aabbs); - - assert_eq!( - containing_aabb.min, - Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY) - ); - assert_eq!( - containing_aabb.max, - Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY) - ); +impl Display for Aabb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{:.2}, {:.2}, {:.2}] -> [{:.2}, {:.2}, {:.2}]", + self.min.x, self.min.y, self.min.z, + self.max.x, self.max.y, self.max.z + ) } +} - #[test] - fn test_ray_aabb_intersection() { - let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); - - // Ray starting outside and hitting the box - let ray1 = Ray::new(Vec3::new(-2.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); - assert!(aabb.intersect_ray(&ray1).is_some()); - - // Ray starting inside the box - let ray2 = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); - assert!(aabb.intersect_ray(&ray2).is_some()); - - // Ray missing the box - let ray3 = Ray::new(Vec3::new(-2.0, 2.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); - assert!(aabb.intersect_ray(&ray3).is_none()); +impl From<&[T]> for Aabb { + fn from(elements: &[T]) -> Self { + Self::containing(elements) } +} - #[test] - fn test_point_containment() { - let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); - - // Test point inside - assert!(aabb.contains_point(Vec3::new(0.0, 0.0, 0.0))); - - // Test point on boundary - assert!(aabb.contains_point(Vec3::new(1.0, 0.0, 0.0))); - // Test point outside - assert!(!aabb.contains_point(Vec3::new(2.0, 0.0, 0.0))); - } - #[test] - fn test_ray_at() { - let ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); - let point = ray.at(2.0); - assert_eq!(point, Vec3::new(2.0, 0.0, 0.0)); - } - #[test] - fn test_degenerate_aabb_as_point() { - let aabb = Aabb::new(Vec3::new(1.0, 1.0, 1.0), Vec3::new(1.0, 1.0, 1.0)); - let ray = Ray::new(Vec3::new(0.0, 1.0, 1.0), Vec3::new(1.0, 0.0, 0.0)); - let intersection = aabb.intersect_ray(&ray); - assert!( - intersection.is_some(), - "Ray should hit the degenerate AABB point" - ); - assert_relative_eq!(intersection.unwrap().into_inner(), 1.0, max_relative = 1e-6); - } - #[test] - fn test_degenerate_aabb_as_line() { - let aabb = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(5.0, 0.0, 0.0)); - let ray = Ray::new(Vec3::new(-1.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); - let intersection = aabb.intersect_ray(&ray); - assert!( - intersection.is_some(), - "Ray should hit the line segment AABB" - ); - assert_relative_eq!(intersection.unwrap().into_inner(), 1.0, max_relative = 1e-6); - } - #[test] - fn test_ray_touching_aabb_boundary() { - let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); - // Ray parallel to one axis and just touches at x = -1 - let ray = Ray::new(Vec3::new(-2.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); - let intersection = aabb.intersect_ray(&ray); - assert!( - intersection.is_some(), - "Ray should intersect exactly at the boundary x = -1" - ); - assert_relative_eq!(intersection.unwrap().into_inner(), 1.0, max_relative = 1e-6); - } - #[test] - fn test_ray_near_corner() { - let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(0.0, 0.0, 0.0)); - // A ray that "just misses" the corner at (-1,-1,-1) - let ray = Ray::new( - Vec3::new(-2.0, -1.000_001, -1.000_001), - Vec3::new(1.0, 0.0, 0.0), - ); - let intersection = aabb.intersect_ray(&ray); - // Depending on precision, this might fail if the intersection logic isn't robust. - // Checking that we correctly return None or an intersection close to the corner. - assert!(intersection.is_none(), "Ray should miss by a tiny margin"); - } - #[test] - fn test_ray_origin_inside_single_aabb() { - let aabb = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(10.0, 10.0, 10.0)); - let ray = Ray::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(1.0, 0.0, 0.0)); // Inside the box - let dist = aabb.intersect_ray(&ray); - assert!( - dist.is_some(), - "Ray from inside should intersect at t=0 or near 0" - ); - assert_relative_eq!(dist.unwrap().into_inner(), 0.0, max_relative = 1e-6); - } - #[test] - fn test_ray_stationary_inside_aabb() { - let aabb = Aabb::new((0.0, 0.0, 0.0), (10.0, 10.0, 10.0)); - let ray = Ray::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(0.0, 0.0, 0.0)); - // With zero direction, we might choose to say intersection is at t=0 if inside, None if outside. - let intersection = aabb.intersect_ray(&ray); - assert_eq!( - intersection, - Some(NotNan::new(0.0).unwrap()), - "Inside and no direction should mean immediate intersection at t=0" - ); - } - #[test] - fn test_ray_just_inside_boundary() { - let aabb = Aabb::new((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)); - let ray = Ray::new(Vec3::new(0.999_999, 0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)); - let intersection = aabb.intersect_ray(&ray); - // If inside, intersection should be at t=0.0 or very close. - assert!(intersection.is_some()); - assert_relative_eq!(intersection.unwrap().into_inner(), 0.0, max_relative = 1e-6); - } -} From aa7b19f6ead8f4bfb756841adb89383d424bc284 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:18:41 -0800 Subject: [PATCH 02/17] feat: better fixes to geo --- crates/geometry/src/aabb.rs | 117 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index a39b3522..bd8f64ea 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -27,6 +27,9 @@ pub struct OrderedAabb { max_x: NotNan, max_y: NotNan, max_z: NotNan, + + // mins: [NotNan; 3], + // maxs: [NotNan; 3], } impl TryFrom for OrderedAabb { @@ -191,8 +194,17 @@ impl Aabb { } } + + pub fn is_empty(&self) -> bool { + + self.min.x > self.max.x || + self.min.y > self.max.y || + self.min.z > self.max.z + } + #[must_use] pub fn overlap(a: &Self, b: &Self) -> Option { + let min_x = a.min.x.max(b.min.x); let min_y = a.min.y.max(b.min.y); let min_z = a.min.z.max(b.min.z); @@ -201,7 +213,6 @@ impl Aabb { let max_y = a.max.y.min(b.max.y); let max_z = a.max.z.min(b.max.z); - // Check if there is an overlap. If any dimension does not overlap, return None. if min_x < max_x && min_y < max_y && min_z < max_z { Some(Self { min: Vec3::new(min_x, min_y, min_z), @@ -214,12 +225,7 @@ impl Aabb { #[must_use] pub fn collides(&self, other: &Self) -> bool { - self.min.x <= other.max.x - && self.max.x >= other.min.x - && self.min.y <= other.max.y - && self.max.y >= other.min.y - && self.min.z <= other.max.z - && self.max.z >= other.min.z + (self.min.cmple(other.max) & self.max.cmpge(other.min)).all() } #[must_use] @@ -234,15 +240,11 @@ impl Aabb { #[must_use] pub fn dist2(&self, point: Vec3) -> f64 { - let point = point.as_dvec3(); - // Clamp the point into the box volume. - let clamped = point.clamp(self.min.as_dvec3(), self.max.as_dvec3()); - - // Distance vector from point to the clamped point inside the box. - let diff = point - clamped; - - // The squared distance. - diff.length_squared() + let point_d = point.as_dvec3(); + let min_d = self.min.as_dvec3(); + let max_d = self.max.as_dvec3(); + let clamped = point_d.clamp(min_d, max_d); + (point_d - clamped).length_squared() } pub fn overlaps<'a, T>( @@ -272,60 +274,56 @@ impl Aabb { #[must_use] pub fn intersect_ray(&self, ray: &Ray) -> Option> { let origin = ray.origin(); - - // If the ray is originating inside the AABB, we can immediately return. if self.contains_point(origin) { return Some(NotNan::new(0.0).unwrap()); } let dir = ray.direction(); let inv_dir = ray.inv_direction(); + + let tx1 = (self.min.x - origin.x) * inv_dir.x; + let tx2 = (self.max.x - origin.x) * inv_dir.x; + let ty1 = (self.min.y - origin.y) * inv_dir.y; + let ty2 = (self.max.y - origin.y) * inv_dir.y; + let tz1 = (self.min.z - origin.z) * inv_dir.z; + let tz2 = (self.max.z - origin.z) * inv_dir.z; + + // Handle parallel rays to each axis + let t_x = if dir.x.abs() < f32::EPSILON { + if origin.x < self.min.x || origin.x > self.max.x { + return None; + } + 0.0 + } else { + tx1.min(tx2).max(ty1.min(ty2)).max(tz1.min(tz2)) + }; - // Initialize t_min and t_max to the range of possible values - let (mut t_min, mut t_max) = (f32::NEG_INFINITY, f32::INFINITY); - - // X-axis - if dir.x != 0.0 { - let tx1 = (self.min.x - origin.x) * inv_dir.x; - let tx2 = (self.max.x - origin.x) * inv_dir.x; - t_min = t_min.max(tx1.min(tx2)); - t_max = t_max.min(tx1.max(tx2)); - } else if origin.x < self.min.x || origin.x > self.max.x { - return None; // Ray is parallel to X slab and outside the slab - } - - // Y-axis - if dir.y != 0.0 { - let ty1 = (self.min.y - origin.y) * inv_dir.y; - let ty2 = (self.max.y - origin.y) * inv_dir.y; - t_min = t_min.max(ty1.min(ty2)); - t_max = t_max.min(ty1.max(ty2)); - } else if origin.y < self.min.y || origin.y > self.max.y { - return None; // Ray is parallel to Y slab and outside the slab - } + let t_y = if dir.y.abs() < f32::EPSILON { + if origin.y < self.min.y || origin.y > self.max.y { + return None; + } + 0.0 + } else { + ty1.min(ty2) + }; - // Z-axis - if dir.z != 0.0 { - let tz1 = (self.min.z - origin.z) * inv_dir.z; - let tz2 = (self.max.z - origin.z) * inv_dir.z; - t_min = t_min.max(tz1.min(tz2)); - t_max = t_max.min(tz1.max(tz2)); - } else if origin.z < self.min.z || origin.z > self.max.z { - return None; // Ray is parallel to Z slab and outside the slab - } + let t_z = if dir.z.abs() < f32::EPSILON { + if origin.z < self.min.z || origin.z > self.max.z { + return None; + } + 0.0 + } else { + tz1.min(tz2) + }; - if t_min > t_max { - return None; - } + let t_min = t_x.max(t_y).max(t_z); + let t_max = tx1.max(tx2).min(ty1.max(ty2)).min(tz1.max(tz2)); - // At this point, t_min and t_max define the intersection range. - // If t_min < 0.0, it means we start “behind” the origin; if t_max < 0.0, no intersection in front. - let t_hit = if t_min >= 0.0 { t_min } else { t_max }; - if t_hit < 0.0 { - return None; + if t_min <= t_max && t_max >= 0.0 { + Some(NotNan::new(if t_min >= 0.0 { t_min } else { t_max }).unwrap()) + } else { + None } - - Some(NotNan::new(t_hit).unwrap()) } #[must_use] @@ -335,7 +333,6 @@ impl Aabb { self } - /// Check if a point is inside the AABB #[must_use] pub fn contains_point(&self, point: Vec3) -> bool { point.cmpge(self.min).all() && point.cmple(self.max).all() From 1cbc158fa989aed7597a3e9fa34b42fd710e7260 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:26:12 -0800 Subject: [PATCH 03/17] revert to begining cause git hates me --- crates/geometry/src/aabb.rs | 247 +++++++++++++++++++++++++++++------- 1 file changed, 202 insertions(+), 45 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 3d846c2c..454f384f 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -1,34 +1,19 @@ -use std::{fmt::{Debug, Display}, ops::Add}; -use glam::{Vec3, Vec3A}; +use std::{ + fmt::{Debug, Display}, + ops::Add, +}; + +use glam::Vec3; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; + use crate::ray::Ray; pub trait HasAabb { fn aabb(&self) -> Aabb; } -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] -pub struct Aabb { - - min: Vec3A, - max: Vec3A, - - #[serde(skip)] - center: Vec3A, - #[serde(skip)] - half_extents: Vec3A, -} - -impl Default for Aabb { - #[inline(always)] - fn default() -> Self { - Self::NULL - } -} - impl HasAabb for Aabb { - #[inline(always)] fn aabb(&self) -> Aabb { *self } @@ -91,30 +76,8 @@ impl FromIterator for Aabb { min = min.min(aabb.min); max = max.max(aabb.max); } - - let center = (min + max) * 0.5; - let half_extents = (max - min) * 0.5; - Self { - min, - max, - center, - half_extents, - } - } -} -// Implement necessary traits -impl Add for Aabb { - type Output = Self; - #[inline(always)] - fn add(self, rhs: Vec3) -> Self::Output { - let rhs = Vec3A::from(rhs); - Self { - min: self.min + rhs, - max: self.max + rhs, - center: self.center + rhs, - half_extents: self.half_extents, - } + Self { min, max } } } @@ -126,6 +89,7 @@ impl Debug for Aabb { impl Display for Aabb { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // write [0.00, 0.00, 0.00] -> [1.00, 1.00, 1.00] write!( f, "[{:.2}, {:.2}, {:.2}] -> [{:.2}, {:.2}, {:.2}]", @@ -430,13 +394,206 @@ impl From<&[T]> for Aabb { } } +#[cfg(test)] +mod tests { + use approx::assert_relative_eq; + use glam::Vec3; + use ordered_float::NotNan; + use crate::{aabb::Aabb, ray::Ray}; + #[test] + fn test_expand_to_fit() { + let mut aabb = Aabb { + min: Vec3::new(0.0, 0.0, 0.0), + max: Vec3::new(1.0, 1.0, 1.0), + }; + let other = Aabb { + min: Vec3::new(-1.0, -1.0, -1.0), + max: Vec3::new(2.0, 2.0, 2.0), + }; + aabb.expand_to_fit(&other); + assert_eq!(aabb.min, Vec3::new(-1.0, -1.0, -1.0)); + assert_eq!(aabb.max, Vec3::new(2.0, 2.0, 2.0)); + } + + #[test] + fn containing_returns_correct_aabb_for_multiple_aabbs() { + let aabbs = vec![ + Aabb { + min: Vec3::new(0.0, 0.0, 0.0), + max: Vec3::new(1.0, 1.0, 1.0), + }, + Aabb { + min: Vec3::new(-1.0, -1.0, -1.0), + max: Vec3::new(2.0, 2.0, 2.0), + }, + Aabb { + min: Vec3::new(0.5, 0.5, 0.5), + max: Vec3::new(1.5, 1.5, 1.5), + }, + ]; + let containing_aabb = Aabb::containing(&aabbs); + assert_eq!(containing_aabb.min, Vec3::new(-1.0, -1.0, -1.0)); + assert_eq!(containing_aabb.max, Vec3::new(2.0, 2.0, 2.0)); + } + #[test] + fn containing_returns_correct_aabb_for_single_aabb() { + let aabbs = vec![Aabb { + min: Vec3::new(0.0, 0.0, 0.0), + max: Vec3::new(1.0, 1.0, 1.0), + }]; + let containing_aabb = Aabb::containing(&aabbs); + assert_eq!(containing_aabb.min, Vec3::new(0.0, 0.0, 0.0)); + assert_eq!(containing_aabb.max, Vec3::new(1.0, 1.0, 1.0)); + } + + #[test] + fn containing_returns_null_aabb_for_empty_input() { + let aabbs: Vec = vec![]; + + let containing_aabb = Aabb::containing(&aabbs); + + assert_eq!( + containing_aabb.min, + Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY) + ); + assert_eq!( + containing_aabb.max, + Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY) + ); + } + + #[test] + fn test_ray_aabb_intersection() { + let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); + + // Ray starting outside and hitting the box + let ray1 = Ray::new(Vec3::new(-2.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); + assert!(aabb.intersect_ray(&ray1).is_some()); + + // Ray starting inside the box + let ray2 = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); + assert!(aabb.intersect_ray(&ray2).is_some()); + + // Ray missing the box + let ray3 = Ray::new(Vec3::new(-2.0, 2.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); + assert!(aabb.intersect_ray(&ray3).is_none()); + } + + #[test] + fn test_point_containment() { + let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); + + // Test point inside + assert!(aabb.contains_point(Vec3::new(0.0, 0.0, 0.0))); + + // Test point on boundary + assert!(aabb.contains_point(Vec3::new(1.0, 0.0, 0.0))); + + // Test point outside + assert!(!aabb.contains_point(Vec3::new(2.0, 0.0, 0.0))); + } + + #[test] + fn test_ray_at() { + let ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); + + let point = ray.at(2.0); + assert_eq!(point, Vec3::new(2.0, 0.0, 0.0)); + } + + #[test] + fn test_degenerate_aabb_as_point() { + let aabb = Aabb::new(Vec3::new(1.0, 1.0, 1.0), Vec3::new(1.0, 1.0, 1.0)); + let ray = Ray::new(Vec3::new(0.0, 1.0, 1.0), Vec3::new(1.0, 0.0, 0.0)); + let intersection = aabb.intersect_ray(&ray); + assert!( + intersection.is_some(), + "Ray should hit the degenerate AABB point" + ); + assert_relative_eq!(intersection.unwrap().into_inner(), 1.0, max_relative = 1e-6); + } + + #[test] + fn test_degenerate_aabb_as_line() { + let aabb = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(5.0, 0.0, 0.0)); + let ray = Ray::new(Vec3::new(-1.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); + let intersection = aabb.intersect_ray(&ray); + assert!( + intersection.is_some(), + "Ray should hit the line segment AABB" + ); + assert_relative_eq!(intersection.unwrap().into_inner(), 1.0, max_relative = 1e-6); + } + + #[test] + fn test_ray_touching_aabb_boundary() { + let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); + // Ray parallel to one axis and just touches at x = -1 + let ray = Ray::new(Vec3::new(-2.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); + let intersection = aabb.intersect_ray(&ray); + assert!( + intersection.is_some(), + "Ray should intersect exactly at the boundary x = -1" + ); + assert_relative_eq!(intersection.unwrap().into_inner(), 1.0, max_relative = 1e-6); + } + + #[test] + fn test_ray_near_corner() { + let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(0.0, 0.0, 0.0)); + // A ray that "just misses" the corner at (-1,-1,-1) + let ray = Ray::new( + Vec3::new(-2.0, -1.000_001, -1.000_001), + Vec3::new(1.0, 0.0, 0.0), + ); + let intersection = aabb.intersect_ray(&ray); + // Depending on precision, this might fail if the intersection logic isn't robust. + // Checking that we correctly return None or an intersection close to the corner. + assert!(intersection.is_none(), "Ray should miss by a tiny margin"); + } + + #[test] + fn test_ray_origin_inside_single_aabb() { + let aabb = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(10.0, 10.0, 10.0)); + let ray = Ray::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(1.0, 0.0, 0.0)); // Inside the box + let dist = aabb.intersect_ray(&ray); + assert!( + dist.is_some(), + "Ray from inside should intersect at t=0 or near 0" + ); + assert_relative_eq!(dist.unwrap().into_inner(), 0.0, max_relative = 1e-6); + } + + #[test] + fn test_ray_stationary_inside_aabb() { + let aabb = Aabb::new((0.0, 0.0, 0.0), (10.0, 10.0, 10.0)); + let ray = Ray::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(0.0, 0.0, 0.0)); + // With zero direction, we might choose to say intersection is at t=0 if inside, None if outside. + let intersection = aabb.intersect_ray(&ray); + assert_eq!( + intersection, + Some(NotNan::new(0.0).unwrap()), + "Inside and no direction should mean immediate intersection at t=0" + ); + } + + #[test] + fn test_ray_just_inside_boundary() { + let aabb = Aabb::new((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)); + let ray = Ray::new(Vec3::new(0.999_999, 0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)); + let intersection = aabb.intersect_ray(&ray); + // If inside, intersection should be at t=0.0 or very close. + assert!(intersection.is_some()); + assert_relative_eq!(intersection.unwrap().into_inner(), 0.0, max_relative = 1e-6); + } +} \ No newline at end of file From 357c87e5ab72783c60d1339cbf0a1f4e9c60cd2d Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:28:31 -0800 Subject: [PATCH 04/17] clean up throughout code, dist2 optimizations --- crates/geometry/src/aabb.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 454f384f..ceac98fc 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -191,8 +191,21 @@ impl Aabb { } } + pub fn is_empty(&self) -> bool { + // Fast check if this is an empty AABB + self.min.x > self.max.x || + self.min.y > self.max.y || + self.min.z > self.max.z + } + + #[must_use] pub fn overlap(a: &Self, b: &Self) -> Option { + + if a.is_empty() || b.is_empty() { + return None; + } + let min_x = a.min.x.max(b.min.x); let min_y = a.min.y.max(b.min.y); let min_z = a.min.z.max(b.min.z); @@ -234,15 +247,11 @@ impl Aabb { #[must_use] pub fn dist2(&self, point: Vec3) -> f64 { - let point = point.as_dvec3(); - // Clamp the point into the box volume. - let clamped = point.clamp(self.min.as_dvec3(), self.max.as_dvec3()); - - // Distance vector from point to the clamped point inside the box. - let diff = point - clamped; - - // The squared distance. - diff.length_squared() + let point_d = point.as_dvec3(); + let min_d = self.min.as_dvec3(); + let max_d = self.max.as_dvec3(); + let clamped = point_d.clamp(min_d, max_d); + (point_d - clamped).length_squared() } pub fn overlaps<'a, T>( From 6dfef71eb2e2f25356a6df8275a546c7dc19932e Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:30:13 -0800 Subject: [PATCH 05/17] interect rays optimizations --- crates/geometry/src/aabb.rs | 64 ++++++++++--------------------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index ceac98fc..505645ee 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -280,61 +280,29 @@ impl Aabb { #[must_use] pub fn intersect_ray(&self, ray: &Ray) -> Option> { + // Optimized with SIMD and reduced branching let origin = ray.origin(); - - // If the ray is originating inside the AABB, we can immediately return. if self.contains_point(origin) { return Some(NotNan::new(0.0).unwrap()); } - + let dir = ray.direction(); let inv_dir = ray.inv_direction(); - - // Initialize t_min and t_max to the range of possible values - let (mut t_min, mut t_max) = (f32::NEG_INFINITY, f32::INFINITY); - - // X-axis - if dir.x != 0.0 { - let tx1 = (self.min.x - origin.x) * inv_dir.x; - let tx2 = (self.max.x - origin.x) * inv_dir.x; - t_min = t_min.max(tx1.min(tx2)); - t_max = t_max.min(tx1.max(tx2)); - } else if origin.x < self.min.x || origin.x > self.max.x { - return None; // Ray is parallel to X slab and outside the slab - } - - // Y-axis - if dir.y != 0.0 { - let ty1 = (self.min.y - origin.y) * inv_dir.y; - let ty2 = (self.max.y - origin.y) * inv_dir.y; - t_min = t_min.max(ty1.min(ty2)); - t_max = t_max.min(ty1.max(ty2)); - } else if origin.y < self.min.y || origin.y > self.max.y { - return None; // Ray is parallel to Y slab and outside the slab - } - - // Z-axis - if dir.z != 0.0 { - let tz1 = (self.min.z - origin.z) * inv_dir.z; - let tz2 = (self.max.z - origin.z) * inv_dir.z; - t_min = t_min.max(tz1.min(tz2)); - t_max = t_max.min(tz1.max(tz2)); - } else if origin.z < self.min.z || origin.z > self.max.z { - return None; // Ray is parallel to Z slab and outside the slab - } - - if t_min > t_max { - return None; - } - - // At this point, t_min and t_max define the intersection range. - // If t_min < 0.0, it means we start “behind” the origin; if t_max < 0.0, no intersection in front. - let t_hit = if t_min >= 0.0 { t_min } else { t_max }; - if t_hit < 0.0 { - return None; + + let t1 = (self.min - origin) * inv_dir; + let t2 = (self.max - origin) * inv_dir; + + let t_min = t1.min(t2); + let t_max = t1.max(t2); + + let t_enter = t_min.max_element(); + let t_exit = t_max.min_element(); + + if t_enter <= t_exit && t_exit >= 0.0 { + Some(NotNan::new(if t_enter >= 0.0 { t_enter } else { t_exit }).unwrap()) + } else { + None } - - Some(NotNan::new(t_hit).unwrap()) } #[must_use] From 1f41a2dfe629f7f99db995d6609ee34bdcc61648 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:31:03 -0800 Subject: [PATCH 06/17] collides optimized to use SIMD operations since glam::Vec3 supports them --- crates/geometry/src/aabb.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 505645ee..609ddc65 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -227,12 +227,7 @@ impl Aabb { #[must_use] pub fn collides(&self, other: &Self) -> bool { - self.min.x <= other.max.x - && self.max.x >= other.min.x - && self.min.y <= other.max.y - && self.max.y >= other.min.y - && self.min.z <= other.max.z - && self.max.z >= other.min.z + (self.min.cmple(other.max) & self.max.cmpge(other.min)).all() } #[must_use] From 5bb7fbe6575e207c2cb7a4bf029a5a9abcb4e6e4 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:31:54 -0800 Subject: [PATCH 07/17] collides_points same opts as collides --- crates/geometry/src/aabb.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 609ddc65..1e329a1e 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -232,12 +232,7 @@ impl Aabb { #[must_use] pub fn collides_point(&self, point: Vec3) -> bool { - self.min.x <= point.x - && point.x <= self.max.x - && self.min.y <= point.y - && point.y <= self.max.y - && self.min.z <= point.z - && point.z <= self.max.z + (self.min.cmple(point) & point.cmple(self.max)).all() } #[must_use] From fae9e26318fd422a7406c8a55b81cc7f8a7ba5f5 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:39:31 -0800 Subject: [PATCH 08/17] cache opts for key function, batch collides, dist2 opts intersect rays opts --- crates/geometry/src/aabb.rs | 49 ++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 1e329a1e..2c5ce31a 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -69,16 +69,16 @@ impl From<[f32; 6]> for Aabb { impl FromIterator for Aabb { fn from_iter>(iter: T) -> Self { - let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY); - let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY); - - for aabb in iter { - min = min.min(aabb.min); - max = max.max(aabb.max); - } - + + let infinity = Vec3::splat(f32::INFINITY); + let neg_infinity = Vec3::splat(f32::NEG_INFINITY); + let (min, max) = iter.into_iter().fold( + (infinity, neg_infinity), + |(min, max), aabb| (min.min(aabb.min), max.max(aabb.max)) + ); Self { min, max } } + } impl Debug for Aabb { @@ -183,6 +183,7 @@ impl Aabb { Self { min, max } } + #[inline] #[must_use] pub fn move_by(&self, offset: Vec3) -> Self { Self { @@ -225,16 +226,24 @@ impl Aabb { } } + #[inline] #[must_use] pub fn collides(&self, other: &Self) -> bool { (self.min.cmple(other.max) & self.max.cmpge(other.min)).all() } + #[inline] #[must_use] pub fn collides_point(&self, point: Vec3) -> bool { (self.min.cmple(point) & point.cmple(self.max)).all() } + pub fn batch_collides(&self, others: &[Self]) -> Vec { + others.iter() + .map(|other| self.collides(other)) + .collect() + } + #[must_use] pub fn dist2(&self, point: Vec3) -> f64 { let point_d = point.as_dvec3(); @@ -257,9 +266,10 @@ impl Aabb { #[must_use] pub fn surface_area(&self) -> f32 { let lens = self.lens(); - 2.0 * lens - .z - .mul_add(lens.x, lens.x.mul_add(lens.y, lens.y * lens.z)) + let xy = lens.x * lens.y; + let yz = lens.y * lens.z; + let xz = lens.x * lens.z; + 2.0 * (xy + yz + xz) } #[must_use] @@ -333,6 +343,7 @@ impl Aabb { (self.min.z + self.max.z) / 2.0 } + #[inline] #[must_use] pub fn lens(&self) -> Vec3 { self.max - self.min @@ -353,6 +364,22 @@ impl Aabb { max: current_max, } } + + // pub fn overlap(a: &Self, b: &Self) -> Option { + // if a.is_empty() || b.is_empty() { + // return None; + // } + + // let min = a.min.max(b.min); + // let max = a.max.min(b.max); + + // // Use SIMD comparison + // if (min.cmple(max)).all() { + // Some(Self { min, max }) + // } else { + // None + // } + // } } impl From<&[T]> for Aabb { From 694b7548700a666aac024799b55a88ffe5cb1294 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:40:03 -0800 Subject: [PATCH 09/17] inline opts for intersect_ray --- crates/geometry/src/aabb.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 2c5ce31a..8bae4b2f 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -278,6 +278,7 @@ impl Aabb { lens.x * lens.y * lens.z } + #[inline] #[must_use] pub fn intersect_ray(&self, ray: &Ray) -> Option> { // Optimized with SIMD and reduced branching From 848c83711bfa217cf45a5d7ac19eb020312086c0 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:26:04 -0800 Subject: [PATCH 10/17] all tests pass! --- crates/geometry/src/aabb.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 8bae4b2f..d21dd4e2 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -288,16 +288,35 @@ impl Aabb { } let dir = ray.direction(); - let inv_dir = ray.inv_direction(); + let inv_dir = dir.map(|v| if v.abs() > 1e-6 { 1.0 / v } else { 0.0 }); + + println!("inv_dir: {}", inv_dir); - let t1 = (self.min - origin) * inv_dir; - let t2 = (self.max - origin) * inv_dir; + let mut t1 = (self.min - origin) * inv_dir; + let mut t2 = (self.max - origin) * inv_dir; + + + for axis in 0..3 { + if dir[axis] == 0.0 { + if origin[axis] < self.min[axis] || origin[axis] > self.max[axis] { + return None; + } + t1[axis] = -f32::INFINITY; + t2[axis] = f32::INFINITY; + } + } + + println!("t1: {}, t2: {}", t1, t2); let t_min = t1.min(t2); let t_max = t1.max(t2); + + println!("t_min: {}, t_max: {}", t_min, t_max); let t_enter = t_min.max_element(); let t_exit = t_max.min_element(); + + println!("t_enter: {}, t_exit: {}", t_enter, t_exit); if t_enter <= t_exit && t_exit >= 0.0 { Some(NotNan::new(if t_enter >= 0.0 { t_enter } else { t_exit }).unwrap()) @@ -532,10 +551,14 @@ mod tests { #[test] fn test_ray_touching_aabb_boundary() { + let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); - // Ray parallel to one axis and just touches at x = -1 + let ray = Ray::new(Vec3::new(-2.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); let intersection = aabb.intersect_ray(&ray); + println!(""); + println!("Intersection {:?}", intersection); + println!(""); assert!( intersection.is_some(), "Ray should intersect exactly at the boundary x = -1" From 17d5d0058813cbee66d810970e080815f32da2cf Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:27:13 -0800 Subject: [PATCH 11/17] clean up comments --- crates/geometry/src/aabb.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index d21dd4e2..a9f3db01 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -281,21 +281,13 @@ impl Aabb { #[inline] #[must_use] pub fn intersect_ray(&self, ray: &Ray) -> Option> { - // Optimized with SIMD and reduced branching let origin = ray.origin(); - if self.contains_point(origin) { - return Some(NotNan::new(0.0).unwrap()); - } - let dir = ray.direction(); - let inv_dir = dir.map(|v| if v.abs() > 1e-6 { 1.0 / v } else { 0.0 }); + let inv_dir = dir.recip(); - println!("inv_dir: {}", inv_dir); - let mut t1 = (self.min - origin) * inv_dir; let mut t2 = (self.max - origin) * inv_dir; - for axis in 0..3 { if dir[axis] == 0.0 { if origin[axis] < self.min[axis] || origin[axis] > self.max[axis] { @@ -306,20 +298,14 @@ impl Aabb { } } - println!("t1: {}, t2: {}", t1, t2); - let t_min = t1.min(t2); let t_max = t1.max(t2); - println!("t_min: {}, t_max: {}", t_min, t_max); - - let t_enter = t_min.max_element(); + let t_enter = t_min.max_element().max(0.0); let t_exit = t_max.min_element(); - println!("t_enter: {}, t_exit: {}", t_enter, t_exit); - - if t_enter <= t_exit && t_exit >= 0.0 { - Some(NotNan::new(if t_enter >= 0.0 { t_enter } else { t_exit }).unwrap()) + if t_enter <= t_exit { + Some(NotNan::new(t_enter).unwrap()) } else { None } From e9d66ea1dcc3d11ce4b12652a858f539b3a64edb Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:32:43 -0800 Subject: [PATCH 12/17] more consice: intersect_ray --- crates/geometry/src/aabb.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index a9f3db01..23c6bf80 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -283,34 +283,33 @@ impl Aabb { pub fn intersect_ray(&self, ray: &Ray) -> Option> { let origin = ray.origin(); let dir = ray.direction(); - let inv_dir = dir.recip(); + let inv_dir = dir.map(|v| v.recip()); let mut t1 = (self.min - origin) * inv_dir; let mut t2 = (self.max - origin) * inv_dir; for axis in 0..3 { if dir[axis] == 0.0 { - if origin[axis] < self.min[axis] || origin[axis] > self.max[axis] { + if !(self.min[axis] <= origin[axis] && origin[axis] <= self.max[axis]) { return None; } t1[axis] = -f32::INFINITY; t2[axis] = f32::INFINITY; } } + - let t_min = t1.min(t2); + let t_min = t1.min(t2).max(Vec3::splat(0.0)); let t_max = t1.max(t2); - let t_enter = t_min.max_element().max(0.0); - let t_exit = t_max.min_element(); - - if t_enter <= t_exit { - Some(NotNan::new(t_enter).unwrap()) + if t_min.max_element() <= t_max.min_element() { + Some(NotNan::new(t_min.max_element()).unwrap()) } else { None } } + #[must_use] pub fn expand(mut self, amount: f32) -> Self { self.min -= Vec3::splat(amount); From 5801e1adf747a2fb9af7e4f131fa2d0cfca81160 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:55:28 -0800 Subject: [PATCH 13/17] clippy, fmt, docs --- crates/geometry/src/aabb.rs | 38 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 23c6bf80..d2751179 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -69,16 +69,15 @@ impl From<[f32; 6]> for Aabb { impl FromIterator for Aabb { fn from_iter>(iter: T) -> Self { - let infinity = Vec3::splat(f32::INFINITY); let neg_infinity = Vec3::splat(f32::NEG_INFINITY); - let (min, max) = iter.into_iter().fold( - (infinity, neg_infinity), - |(min, max), aabb| (min.min(aabb.min), max.max(aabb.max)) - ); + let (min, max) = iter + .into_iter() + .fold((infinity, neg_infinity), |(min, max), aabb| { + (min.min(aabb.min), max.max(aabb.max)) + }); Self { min, max } } - } impl Debug for Aabb { @@ -192,21 +191,18 @@ impl Aabb { } } + #[must_use] pub fn is_empty(&self) -> bool { // Fast check if this is an empty AABB - self.min.x > self.max.x || - self.min.y > self.max.y || - self.min.z > self.max.z + self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z } - #[must_use] pub fn overlap(a: &Self, b: &Self) -> Option { - if a.is_empty() || b.is_empty() { return None; } - + let min_x = a.min.x.max(b.min.x); let min_y = a.min.y.max(b.min.y); let min_z = a.min.z.max(b.min.z); @@ -238,10 +234,9 @@ impl Aabb { (self.min.cmple(point) & point.cmple(self.max)).all() } + #[must_use] pub fn batch_collides(&self, others: &[Self]) -> Vec { - others.iter() - .map(|other| self.collides(other)) - .collect() + others.iter().map(|other| self.collides(other)).collect() } #[must_use] @@ -283,7 +278,7 @@ impl Aabb { pub fn intersect_ray(&self, ray: &Ray) -> Option> { let origin = ray.origin(); let dir = ray.direction(); - let inv_dir = dir.map(|v| v.recip()); + let inv_dir = dir.map(f32::recip); let mut t1 = (self.min - origin) * inv_dir; let mut t2 = (self.max - origin) * inv_dir; @@ -297,7 +292,6 @@ impl Aabb { t2[axis] = f32::INFINITY; } } - let t_min = t1.min(t2).max(Vec3::splat(0.0)); let t_max = t1.max(t2); @@ -309,7 +303,6 @@ impl Aabb { } } - #[must_use] pub fn expand(mut self, amount: f32) -> Self { self.min -= Vec3::splat(amount); @@ -374,10 +367,10 @@ impl Aabb { // if a.is_empty() || b.is_empty() { // return None; // } - + // let min = a.min.max(b.min); // let max = a.max.min(b.max); - + // // Use SIMD comparison // if (min.cmple(max)).all() { // Some(Self { min, max }) @@ -536,9 +529,8 @@ mod tests { #[test] fn test_ray_touching_aabb_boundary() { - let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); - + let ray = Ray::new(Vec3::new(-2.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); let intersection = aabb.intersect_ray(&ray); println!(""); @@ -599,4 +591,4 @@ mod tests { assert!(intersection.is_some()); assert_relative_eq!(intersection.unwrap().into_inner(), 0.0, max_relative = 1e-6); } -} \ No newline at end of file +} From 4f462449068e48f5087d7db30ba42a0b8589745d Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:00:44 -0800 Subject: [PATCH 14/17] fix test --- crates/geometry/src/aabb.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index d2751179..6f3bc850 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -533,9 +533,6 @@ mod tests { let ray = Ray::new(Vec3::new(-2.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)); let intersection = aabb.intersect_ray(&ray); - println!(""); - println!("Intersection {:?}", intersection); - println!(""); assert!( intersection.is_some(), "Ray should intersect exactly at the boundary x = -1" From 34e4e112b3d35eb5601af606d8e4f08095456e4f Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:36:24 -0800 Subject: [PATCH 15/17] pleasing TestingPlant --- crates/geometry/src/aabb.rs | 49 +++++++++---------------------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 6f3bc850..f95bddd2 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -191,35 +191,23 @@ impl Aabb { } } - #[must_use] - pub fn is_empty(&self) -> bool { - // Fast check if this is an empty AABB - self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z - } + // #[must_use] + // pub fn is_empty(&self) -> bool { + // // Fast check if this is an empty AABB + // self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z + // } #[must_use] pub fn overlap(a: &Self, b: &Self) -> Option { - if a.is_empty() || b.is_empty() { - return None; - } - - let min_x = a.min.x.max(b.min.x); - let min_y = a.min.y.max(b.min.y); - let min_z = a.min.z.max(b.min.z); - let max_x = a.max.x.min(b.max.x); - let max_y = a.max.y.min(b.max.y); - let max_z = a.max.z.min(b.max.z); - - // Check if there is an overlap. If any dimension does not overlap, return None. - if min_x < max_x && min_y < max_y && min_z < max_z { - Some(Self { - min: Vec3::new(min_x, min_y, min_z), - max: Vec3::new(max_x, max_y, max_z), - }) + let min = a.min.max(b.min); + let max = a.max.min(b.max); + if min.cmplt(max).all() { + Some(Self { min, max }) } else { None } + } #[inline] @@ -278,7 +266,7 @@ impl Aabb { pub fn intersect_ray(&self, ray: &Ray) -> Option> { let origin = ray.origin(); let dir = ray.direction(); - let inv_dir = dir.map(f32::recip); + let inv_dir = ray.inv_direction(); let mut t1 = (self.min - origin) * inv_dir; let mut t2 = (self.max - origin) * inv_dir; @@ -363,21 +351,6 @@ impl Aabb { } } - // pub fn overlap(a: &Self, b: &Self) -> Option { - // if a.is_empty() || b.is_empty() { - // return None; - // } - - // let min = a.min.max(b.min); - // let max = a.max.min(b.max); - - // // Use SIMD comparison - // if (min.cmple(max)).all() { - // Some(Self { min, max }) - // } else { - // None - // } - // } } impl From<&[T]> for Aabb { From d7946dfe78d60e644371983cbcd1a4fe0c9017b3 Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:47:15 -0800 Subject: [PATCH 16/17] remove is_empty --- crates/geometry/src/aabb.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index f95bddd2..4d3147fa 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -191,12 +191,6 @@ impl Aabb { } } - // #[must_use] - // pub fn is_empty(&self) -> bool { - // // Fast check if this is an empty AABB - // self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z - // } - #[must_use] pub fn overlap(a: &Self, b: &Self) -> Option { From 315dbcf0e6401584579c79959768cccbab7b72fa Mon Sep 17 00:00:00 2001 From: Shidowy <101477459+Shidowy@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:49:31 -0800 Subject: [PATCH 17/17] fmt --- crates/geometry/src/aabb.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/geometry/src/aabb.rs b/crates/geometry/src/aabb.rs index 4d3147fa..f540e5cd 100644 --- a/crates/geometry/src/aabb.rs +++ b/crates/geometry/src/aabb.rs @@ -193,7 +193,6 @@ impl Aabb { #[must_use] pub fn overlap(a: &Self, b: &Self) -> Option { - let min = a.min.max(b.min); let max = a.max.min(b.max); if min.cmplt(max).all() { @@ -201,7 +200,6 @@ impl Aabb { } else { None } - } #[inline] @@ -344,7 +342,6 @@ impl Aabb { max: current_max, } } - } impl From<&[T]> for Aabb {