diff --git a/Cargo.toml b/Cargo.toml index ace2d46..b948780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ verbose = [] async = [] no-default-baking = [] detailed-layers = [] -serde = ["glam/serde", "bvh2d/serde", "dep:serde", "rerecast/serialize"] +serde = ["glam/serde", "rstar/serde", "dep:serde", "rerecast/serialize"] recast = [] [dependencies] @@ -27,7 +27,7 @@ tracing = { version = "0.1", optional = true } hashbrown = { version = "0.16" } glam = { version = "0.30.8", features = ["approx"] } smallvec = { version = "1.15", features = ["union", "const_generics"] } -bvh2d = { version = "0.7" } +rstar = { version = "0.12", git = "https://github.com/georust/rstar" } serde = { version = "1.0", features = ["derive"], optional = true } geo = "0.32.0" log = "0.4" diff --git a/src/layers.rs b/src/layers.rs index 1effec6..b3792a1 100644 --- a/src/layers.rs +++ b/src/layers.rs @@ -1,8 +1,8 @@ #[cfg(feature = "tracing")] use tracing::instrument; -use bvh2d::bvh2d::BVH2d; use glam::{vec2, Vec2}; +use rstar::RTree; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -23,7 +23,7 @@ pub struct Layer { #[cfg(feature = "detailed-layers")] #[cfg_attr(docsrs, doc(cfg(feature = "detailed-layers")))] pub scale: Vec2, - pub(crate) baked_polygons: Option, + pub(crate) baked_polygons: Option>, pub(crate) islands: Option>, /// Height of each vertex. Must either have zero elements to ignore heights, or the same length as vertices. pub height: Vec, @@ -110,32 +110,30 @@ impl Layer { pub fn bake_polygon_finder(&mut self) { let bounded_polygons = self .polygons - .iter_mut() - .map(|polygon| BoundedPolygon { - aabb: polygon.vertices.iter().fold( - (vec2(f32::MAX, f32::MAX), Vec2::ZERO), + .iter() + .enumerate() + .map(|(index, polygon)| { + let (min, max) = polygon.vertices.iter().fold( + (vec2(f32::MAX, f32::MAX), vec2(f32::MIN, f32::MIN)), |mut aabb, v| { if let Some(v) = self.vertices.get(*v as usize) { - if v.coords.x < aabb.0.x { - aabb.0.x = v.coords.x; - } - if v.coords.y < aabb.0.y { - aabb.0.y = v.coords.y; - } - if v.coords.x > aabb.1.x { - aabb.1.x = v.coords.x; - } - if v.coords.y > aabb.1.y { - aabb.1.y = v.coords.y; - } + aabb.0.x = aabb.0.x.min(v.coords.x); + aabb.0.y = aabb.0.y.min(v.coords.y); + aabb.1.x = aabb.1.x.max(v.coords.x); + aabb.1.y = aabb.1.y.max(v.coords.y); } aabb }, - ), + ); + BoundedPolygon { + index, + aabb_min: [min.x, min.y], + aabb_max: [max.x, max.y], + } }) .collect::>(); - self.baked_polygons = Some(BVH2d::build(&bounded_polygons)); + self.baked_polygons = Some(RTree::bulk_load(bounded_polygons)); } /// Create a `Layer` from a list of [`Vertex`] and [`Polygon`]. @@ -178,16 +176,39 @@ impl Layer { &'a self, point: &'a Vec2, ) -> impl Iterator + use<'a> { + let query_point = [point.x, point.y]; self.baked_polygons .as_ref() .unwrap() - .contains_iterator(point) - .filter_map(|index| { - self.point_in_polygon(*point, &self.polygons[index]) - .then_some(index as u32) + .locate_in_envelope_intersecting(rstar::AABB::from_point(query_point)) + .filter_map(|bp| { + self.point_in_polygon(*point, &self.polygons[bp.index]) + .then_some(bp.index as u32) }) } + /// Find the first polygon containing the point using internal iteration (no SmallVec allocation). + #[cfg_attr(feature = "tracing", instrument(skip_all))] + #[inline(always)] + pub(crate) fn find_first_point_location_baked(&self, point: &Vec2) -> Option { + use core::ops::ControlFlow; + let query_point = [point.x, point.y]; + let mut result = None; + let _ = self + .baked_polygons + .as_ref() + .unwrap() + .locate_in_envelope_intersecting_int(rstar::AABB::from_point(query_point), |bp| { + if self.point_in_polygon(*point, &self.polygons[bp.index]) { + result = Some(bp.index as u32); + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + result + } + #[cfg_attr(feature = "tracing", instrument(skip_all))] #[inline(always)] fn point_in_polygon(&self, point: Vec2, polygon: &Polygon) -> bool { @@ -239,7 +260,7 @@ impl Layer { if self.baked_polygons.is_none() { self.get_point_locations_unit(point).next() } else { - self.get_point_locations_unit_baked(&point).next() + self.find_first_point_location_baked(&point) } .unwrap_or(u32::MAX) }) @@ -313,7 +334,7 @@ impl Layer { let poly = if self.baked_polygons.is_none() { self.get_point_locations_unit(new_point).next() } else { - self.get_point_locations_unit_baked(&new_point).next() + self.find_first_point_location_baked(&new_point) } .unwrap_or(u32::MAX); @@ -385,7 +406,7 @@ impl Layer { let poly = if self.baked_polygons.is_none() { self.get_point_locations_unit(point).next() } else { - self.get_point_locations_unit_baked(&point).next() + self.find_first_point_location_baked(&point) } .unwrap_or(u32::MAX); if poly != u32::MAX { diff --git a/src/lib.rs b/src/lib.rs index 63b1dfd..075bf7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,6 @@ use std::{ fmt::{self, Debug, Display}, }; -use bvh2d::aabb::{Bounded, AABB}; use glam::{FloatExt, Vec2, Vec3, Vec3Swizzles}; use helpers::{line_intersect_segment, Vec2Helper, EPSILON}; @@ -316,13 +315,18 @@ impl Mesh { } } +#[derive(Debug, Clone)] struct BoundedPolygon { - aabb: (Vec2, Vec2), + index: usize, + aabb_min: [f32; 2], + aabb_max: [f32; 2], } -impl Bounded for BoundedPolygon { - fn aabb(&self) -> AABB { - AABB::with_bounds(self.aabb.0, self.aabb.1) +impl rstar::RTreeObject for BoundedPolygon { + type Envelope = rstar::AABB<[f32; 2]>; + + fn envelope(&self) -> Self::Envelope { + rstar::AABB::from_bounds(self.aabb_min, self.aabb_max) } }