#![cfg_attr(all(target_arch = "wasm32", boxddd_wasm_provider), allow(dead_code))]
use crate::collision::{BoxCastInput, RayCastInput};
use crate::core::{
box3d_lock,
callback_state::{self, LocalCallbackState},
provenance::{OwnerToken, ResourceToken, allocate_owner_token, allocate_resource_token},
validation,
};
use crate::error::{Error, HandleKind, InvalidValueReason, Result};
use crate::query::TreeStats;
use crate::types::{Aabb, Vec3};
use boxddd_sys::ffi;
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::rc::Rc;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct DynamicTreeProxyId {
index: i32,
owner: OwnerToken,
resource: ResourceToken,
}
impl DynamicTreeProxyId {
#[inline]
const fn new(index: i32, owner: OwnerToken, resource: ResourceToken) -> Self {
Self {
index,
owner,
resource,
}
}
#[inline]
const fn into_raw(self) -> i32 {
self.index
}
}
impl fmt::Debug for DynamicTreeProxyId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("DynamicTreeProxyId(..)")
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DynamicTreeProxy {
pub aabb: Aabb,
pub category_bits: u64,
pub user_data: u64,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DynamicTreeFilter {
pub mask_bits: u64,
pub require_all_bits: bool,
}
impl DynamicTreeFilter {
#[inline]
pub const fn new(mask_bits: u64) -> Self {
Self {
mask_bits,
require_all_bits: false,
}
}
#[inline]
pub const fn require_all_bits(mut self, require_all_bits: bool) -> Self {
self.require_all_bits = require_all_bits;
self
}
}
impl Default for DynamicTreeFilter {
fn default() -> Self {
Self {
mask_bits: u64::MAX,
require_all_bits: false,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DynamicTreeHit {
pub proxy_id: DynamicTreeProxyId,
pub user_data: u64,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DynamicTreeClosestHit {
pub min_distance_squared: f32,
pub proxy_id: DynamicTreeProxyId,
pub user_data: u64,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DynamicTreeClosestResult {
pub stats: TreeStats,
pub min_distance_squared: f32,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DynamicTreeRayCastHit {
pub input: RayCastInput,
pub proxy_id: DynamicTreeProxyId,
pub user_data: u64,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DynamicTreeBoxCastHit {
pub input: BoxCastInput,
pub proxy_id: DynamicTreeProxyId,
pub user_data: u64,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DynamicTreeCastControl {
Continue,
Clip(f32),
Skip,
Terminate,
}
impl DynamicTreeCastControl {
fn into_raw(self, max_fraction: f32) -> Result<f32> {
match self {
Self::Continue => Ok(max_fraction),
Self::Clip(fraction) => {
validation::finite("dynamic_tree.cast.clip_fraction", fraction)?;
if (0.0..=max_fraction).contains(&fraction) {
Ok(fraction)
} else {
Err(validation::invalid(
"dynamic_tree.cast.clip_fraction",
InvalidValueReason::OutOfRange,
))
}
}
Self::Skip => Ok(-1.0),
Self::Terminate => Ok(0.0),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
struct ProxyEntry {
resource: ResourceToken,
proxy: DynamicTreeProxy,
}
pub struct DynamicTree {
raw: ffi::b3DynamicTree,
owner: OwnerToken,
proxies: HashMap<i32, ProxyEntry>,
has_enlarged_nodes: bool,
_not_send_sync: PhantomData<Rc<()>>,
}
impl DynamicTree {
pub fn new() -> Result<Self> {
Self::with_capacity(0)
}
pub fn with_capacity(proxy_capacity: usize) -> Result<Self> {
if proxy_capacity > i32::MAX as usize / 2 {
return Err(validation::invalid(
"dynamic_tree.proxy_capacity",
InvalidValueReason::OutOfRange,
));
}
let proxy_capacity = i32::try_from(proxy_capacity).map_err(|_| {
validation::invalid(
"dynamic_tree.proxy_capacity",
InvalidValueReason::OutOfRange,
)
})?;
callback_state::check_not_in_callback()?;
let owner = allocate_owner_token()?;
let _guard = box3d_lock::lock();
let raw = unsafe { ffi::b3DynamicTree_Create(proxy_capacity) };
if raw.nodes.is_null() {
return Err(Error::NativeFailure);
}
Ok(Self {
raw,
owner,
proxies: HashMap::new(),
has_enlarged_nodes: false,
_not_send_sync: PhantomData,
})
}
pub fn create_proxy(&mut self, aabb: Aabb, user_data: u64) -> Result<DynamicTreeProxyId> {
self.create_proxy_with_category_bits(aabb, u64::MAX, user_data)
}
pub fn create_proxy_with_category_bits(
&mut self,
aabb: Aabb,
category_bits: u64,
user_data: u64,
) -> Result<DynamicTreeProxyId> {
callback_state::check_not_in_callback()?;
let aabb = aabb.validate()?;
self.proxies
.try_reserve(1)
.map_err(|_| Error::AllocationFailed)?;
let resource = allocate_resource_token()?;
let _guard = box3d_lock::lock();
let proxy_id = unsafe {
ffi::b3DynamicTree_CreateProxy(&mut self.raw, aabb.into_raw(), category_bits, user_data)
};
if proxy_id < 0 {
return Err(Error::NativeFailure);
}
let previous = self.proxies.insert(
proxy_id,
ProxyEntry {
resource,
proxy: DynamicTreeProxy {
aabb,
category_bits,
user_data,
},
},
);
debug_assert!(previous.is_none(), "native proxy index reused while active");
Ok(DynamicTreeProxyId::new(proxy_id, self.owner, resource))
}
pub fn destroy_proxy(&mut self, proxy_id: DynamicTreeProxyId) -> Result<()> {
callback_state::check_not_in_callback()?;
let proxy_index = self.proxy_index(proxy_id)?;
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_DestroyProxy(&mut self.raw, proxy_index) };
self.proxies.remove(&proxy_index);
if self.proxies.is_empty() {
self.has_enlarged_nodes = false;
}
Ok(())
}
pub fn move_proxy(&mut self, proxy_id: DynamicTreeProxyId, aabb: Aabb) -> Result<()> {
callback_state::check_not_in_callback()?;
let proxy_index = self.proxy_index(proxy_id)?;
let aabb = aabb.validate()?;
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_MoveProxy(&mut self.raw, proxy_index, aabb.into_raw()) };
self.proxies
.get_mut(&proxy_index)
.expect("proxy index validated")
.proxy
.aabb = aabb;
Ok(())
}
pub fn enlarge_proxy(&mut self, proxy_id: DynamicTreeProxyId, aabb: Aabb) -> Result<()> {
callback_state::check_not_in_callback()?;
let proxy_index = self.proxy_index(proxy_id)?;
let aabb = aabb.validate()?;
let current = self
.proxies
.get(&proxy_index)
.expect("proxy index validated")
.proxy
.aabb;
if !aabb_contains(aabb, current) || aabb_contains(current, aabb) {
return Err(validation::invalid(
"dynamic_tree.enlarge_proxy.aabb",
InvalidValueReason::InvalidCombination,
));
}
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_EnlargeProxy(&mut self.raw, proxy_index, aabb.into_raw()) };
self.proxies
.get_mut(&proxy_index)
.expect("proxy index validated")
.proxy
.aabb = aabb;
self.has_enlarged_nodes = true;
Ok(())
}
pub fn set_category_bits(
&mut self,
proxy_id: DynamicTreeProxyId,
category_bits: u64,
) -> Result<()> {
callback_state::check_not_in_callback()?;
let proxy_index = self.proxy_index(proxy_id)?;
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_SetCategoryBits(&mut self.raw, proxy_index, category_bits) };
self.proxies
.get_mut(&proxy_index)
.expect("proxy index validated")
.proxy
.category_bits = category_bits;
Ok(())
}
pub fn category_bits(&mut self, proxy_id: DynamicTreeProxyId) -> Result<u64> {
callback_state::check_not_in_callback()?;
let proxy_index = self.proxy_index(proxy_id)?;
let _guard = box3d_lock::lock();
Ok(unsafe { ffi::b3DynamicTree_GetCategoryBits(&mut self.raw, proxy_index) })
}
pub fn proxy(&self, proxy_id: DynamicTreeProxyId) -> Result<DynamicTreeProxy> {
callback_state::check_not_in_callback()?;
Ok(self.proxy_entry(proxy_id)?.proxy)
}
pub fn contains_proxy(&self, proxy_id: DynamicTreeProxyId) -> bool {
self.proxy_entry(proxy_id).is_ok()
}
pub fn proxy_count(&self) -> Result<usize> {
callback_state::check_not_in_callback()?;
let _guard = box3d_lock::lock();
let count = unsafe { ffi::b3DynamicTree_GetProxyCount(&self.raw) };
usize::try_from(count).map_err(|_| Error::NativeFailure)
}
pub fn byte_count(&self) -> Result<usize> {
callback_state::check_not_in_callback()?;
let _guard = box3d_lock::lock();
let count = unsafe { ffi::b3DynamicTree_GetByteCount(&self.raw) };
usize::try_from(count).map_err(|_| Error::NativeFailure)
}
pub fn height(&self) -> Result<i32> {
callback_state::check_not_in_callback()?;
let _guard = box3d_lock::lock();
Ok(unsafe { ffi::b3DynamicTree_GetHeight(&self.raw) })
}
pub fn area_ratio(&self) -> Result<f32> {
callback_state::check_not_in_callback()?;
let _guard = box3d_lock::lock();
let ratio = unsafe { ffi::b3DynamicTree_GetAreaRatio(&self.raw) };
if ratio.is_finite() && ratio >= 0.0 {
Ok(ratio)
} else {
Err(Error::NativeFailure)
}
}
pub fn root_bounds(&self) -> Result<Option<Aabb>> {
callback_state::check_not_in_callback()?;
if self.proxies.is_empty() {
return Ok(None);
}
let _guard = box3d_lock::lock();
let aabb = Aabb::from_raw(unsafe { ffi::b3DynamicTree_GetRootBounds(&self.raw) });
Ok(Some(aabb.validate().map_err(|_| Error::NativeFailure)?))
}
pub fn rebuild(&mut self, full_build: bool) -> Result<usize> {
callback_state::check_not_in_callback()?;
let _guard = box3d_lock::lock();
let count = unsafe { ffi::b3DynamicTree_Rebuild(&mut self.raw, full_build) };
self.has_enlarged_nodes = false;
usize::try_from(count).map_err(|_| Error::NativeFailure)
}
pub fn validate(&self) -> Result<()> {
callback_state::check_not_in_callback()?;
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_Validate(&self.raw) };
Ok(())
}
pub fn validate_no_enlarged(&self) -> Result<()> {
callback_state::check_not_in_callback()?;
if self.has_enlarged_nodes {
return Err(validation::invalid(
"dynamic_tree.enlarged_nodes",
InvalidValueReason::InvalidCombination,
));
}
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_ValidateNoEnlarged(&self.raw) };
Ok(())
}
pub fn query(&self, aabb: Aabb, filter: DynamicTreeFilter) -> Result<Vec<DynamicTreeHit>> {
let mut out = Vec::new();
self.query_into(aabb, filter, &mut out)?;
Ok(out)
}
pub fn query_into(
&self,
aabb: Aabb,
filter: DynamicTreeFilter,
out: &mut Vec<DynamicTreeHit>,
) -> Result<TreeStats> {
out.clear();
self.visit_query(aabb, filter, |hit| {
out.push(hit);
true
})
}
pub fn visit_query<F>(
&self,
aabb: Aabb,
filter: DynamicTreeFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(DynamicTreeHit) -> bool,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (aabb, filter, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let aabb = aabb.validate()?;
let mut ctx = QueryContext {
visitor,
proxies: &self.proxies as *const HashMap<i32, ProxyEntry>,
owner: self.owner,
state: LocalCallbackState::new(),
};
let _guard = box3d_lock::lock();
let stats = unsafe {
ffi::b3DynamicTree_Query(
&self.raw,
aabb.into_raw(),
filter.mask_bits,
filter.require_all_bits,
Some(query_trampoline::<F>),
(&mut ctx as *mut QueryContext<_>).cast(),
)
};
ctx.state.drain()?;
Ok(TreeStats::from_raw(stats))
}
}
pub fn visit_query_closest<F>(
&self,
point: impl Into<Vec3>,
filter: DynamicTreeFilter,
min_distance_squared: f32,
visitor: F,
) -> Result<DynamicTreeClosestResult>
where
F: FnMut(DynamicTreeClosestHit) -> f32,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (point, filter, min_distance_squared, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let point = point.into();
validation::vec3("dynamic_tree.query_closest.point", point)?;
validation::nonnegative(
"dynamic_tree.query_closest.min_distance_squared",
min_distance_squared,
)?;
let mut ctx = ClosestContext {
visitor,
proxies: &self.proxies as *const HashMap<i32, ProxyEntry>,
owner: self.owner,
state: LocalCallbackState::new(),
};
let mut min_distance_squared = min_distance_squared;
let _guard = box3d_lock::lock();
let stats = unsafe {
ffi::b3DynamicTree_QueryClosest(
&self.raw,
point.into_raw(),
filter.mask_bits,
filter.require_all_bits,
Some(closest_trampoline::<F>),
(&mut ctx as *mut ClosestContext<_>).cast(),
&mut min_distance_squared,
)
};
ctx.state.drain()?;
if !min_distance_squared.is_finite() || min_distance_squared < 0.0 {
return Err(Error::NativeFailure);
}
Ok(DynamicTreeClosestResult {
stats: TreeStats::from_raw(stats),
min_distance_squared,
})
}
}
pub fn visit_ray_cast<F>(
&self,
input: RayCastInput,
filter: DynamicTreeFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(DynamicTreeRayCastHit) -> DynamicTreeCastControl,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (input, filter, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let input = input.validate()?;
let raw_input = input.raw();
let _guard = box3d_lock::lock();
if !unsafe { ffi::b3IsValidRay(&raw_input) } {
return Err(validation::invalid(
"ray_cast.max_fraction",
InvalidValueReason::OutOfRange,
));
}
let mut ctx = RayCastContext {
visitor,
proxies: &self.proxies as *const HashMap<i32, ProxyEntry>,
owner: self.owner,
state: LocalCallbackState::new(),
};
let stats = unsafe {
ffi::b3DynamicTree_RayCast(
&self.raw,
&raw_input,
filter.mask_bits,
filter.require_all_bits,
Some(ray_cast_trampoline::<F>),
(&mut ctx as *mut RayCastContext<_>).cast(),
)
};
ctx.state.drain()?;
Ok(TreeStats::from_raw(stats))
}
}
pub fn visit_box_cast<F>(
&self,
input: BoxCastInput,
filter: DynamicTreeFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(DynamicTreeBoxCastHit) -> DynamicTreeCastControl,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (input, filter, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let raw_input = input.validate()?.raw();
let mut ctx = BoxCastContext {
visitor,
proxies: &self.proxies as *const HashMap<i32, ProxyEntry>,
owner: self.owner,
state: LocalCallbackState::new(),
};
let _guard = box3d_lock::lock();
let stats = unsafe {
ffi::b3DynamicTree_BoxCast(
&self.raw,
&raw_input,
filter.mask_bits,
filter.require_all_bits,
Some(box_cast_trampoline::<F>),
(&mut ctx as *mut BoxCastContext<_>).cast(),
)
};
ctx.state.drain()?;
Ok(TreeStats::from_raw(stats))
}
}
fn proxy_index(&self, proxy_id: DynamicTreeProxyId) -> Result<i32> {
let index = proxy_id.into_raw();
self.proxy_entry(proxy_id)?;
Ok(index)
}
fn proxy_entry(&self, proxy_id: DynamicTreeProxyId) -> Result<&ProxyEntry> {
if proxy_id.owner != self.owner {
return Err(Error::ForeignHandle {
kind: HandleKind::DynamicTreeProxy,
});
}
let index = proxy_id.into_raw();
let Some(entry) = self.proxies.get(&index) else {
return Err(Error::StaleHandle {
kind: HandleKind::DynamicTreeProxy,
});
};
if proxy_id.resource == entry.resource {
Ok(entry)
} else {
Err(Error::StaleHandle {
kind: HandleKind::DynamicTreeProxy,
})
}
}
}
impl Drop for DynamicTree {
fn drop(&mut self) {
if self.raw.nodes.is_null() {
return;
}
let _guard = box3d_lock::lock();
unsafe { ffi::b3DynamicTree_Destroy(&mut self.raw) };
}
}
struct QueryContext<F> {
visitor: F,
proxies: *const HashMap<i32, ProxyEntry>,
owner: OwnerToken,
state: LocalCallbackState,
}
unsafe extern "C" fn query_trampoline<F>(
proxy_id: i32,
user_data: u64,
context: *mut std::ffi::c_void,
) -> bool
where
F: FnMut(DynamicTreeHit) -> bool,
{
let ctx = unsafe { &mut *context.cast::<QueryContext<F>>() };
let Some(proxy_id) = proxy_id_from_context(proxy_id, ctx.proxies, ctx.owner) else {
return ctx.state.fail(Error::NativeFailure, false);
};
let hit = DynamicTreeHit {
proxy_id,
user_data,
};
ctx.state.invoke(false, || (ctx.visitor)(hit))
}
struct ClosestContext<F> {
visitor: F,
proxies: *const HashMap<i32, ProxyEntry>,
owner: OwnerToken,
state: LocalCallbackState,
}
unsafe extern "C" fn closest_trampoline<F>(
min_distance_squared: f32,
proxy_id: i32,
user_data: u64,
context: *mut std::ffi::c_void,
) -> f32
where
F: FnMut(DynamicTreeClosestHit) -> f32,
{
let ctx = unsafe { &mut *context.cast::<ClosestContext<F>>() };
let Some(proxy_id) = proxy_id_from_context(proxy_id, ctx.proxies, ctx.owner) else {
return ctx.state.fail(Error::NativeFailure, min_distance_squared);
};
let hit = DynamicTreeClosestHit {
min_distance_squared,
proxy_id,
user_data,
};
let next_min = ctx
.state
.invoke(min_distance_squared, || (ctx.visitor)(hit));
if next_min.is_finite() && next_min >= 0.0 {
next_min
} else {
min_distance_squared
}
}
struct RayCastContext<F> {
visitor: F,
proxies: *const HashMap<i32, ProxyEntry>,
owner: OwnerToken,
state: LocalCallbackState,
}
unsafe extern "C" fn ray_cast_trampoline<F>(
input: *const ffi::b3RayCastInput,
proxy_id: i32,
user_data: u64,
context: *mut std::ffi::c_void,
) -> f32
where
F: FnMut(DynamicTreeRayCastHit) -> DynamicTreeCastControl,
{
let ctx = unsafe { &mut *context.cast::<RayCastContext<F>>() };
if input.is_null() {
return ctx.state.fail(Error::NativeFailure, 0.0);
}
let input = unsafe { *input };
let Ok(input) = RayCastInput::with_max_fraction(
Vec3::from_raw(input.origin),
Vec3::from_raw(input.translation),
input.maxFraction,
) else {
return ctx.state.fail(Error::NativeFailure, 0.0);
};
let Some(proxy_id) = proxy_id_from_context(proxy_id, ctx.proxies, ctx.owner) else {
return ctx.state.fail(Error::NativeFailure, 0.0);
};
let hit = DynamicTreeRayCastHit {
input,
proxy_id,
user_data,
};
let control = ctx
.state
.invoke(DynamicTreeCastControl::Terminate, || (ctx.visitor)(hit));
match control.into_raw(input.max_fraction) {
Ok(next_fraction) => next_fraction,
Err(error) => ctx.state.fail(error, 0.0),
}
}
struct BoxCastContext<F> {
visitor: F,
proxies: *const HashMap<i32, ProxyEntry>,
owner: OwnerToken,
state: LocalCallbackState,
}
unsafe extern "C" fn box_cast_trampoline<F>(
input: *const ffi::b3BoxCastInput,
proxy_id: i32,
user_data: u64,
context: *mut std::ffi::c_void,
) -> f32
where
F: FnMut(DynamicTreeBoxCastHit) -> DynamicTreeCastControl,
{
let ctx = unsafe { &mut *context.cast::<BoxCastContext<F>>() };
if input.is_null() {
return ctx.state.fail(Error::NativeFailure, 0.0);
}
let input = unsafe { *input };
let Ok(input) = BoxCastInput::with_max_fraction(
Aabb::from_raw(input.box_),
Vec3::from_raw(input.translation),
input.maxFraction,
) else {
return ctx.state.fail(Error::NativeFailure, 0.0);
};
let Some(proxy_id) = proxy_id_from_context(proxy_id, ctx.proxies, ctx.owner) else {
return ctx.state.fail(Error::NativeFailure, 0.0);
};
let hit = DynamicTreeBoxCastHit {
input,
proxy_id,
user_data,
};
let control = ctx
.state
.invoke(DynamicTreeCastControl::Terminate, || (ctx.visitor)(hit));
match control.into_raw(input.max_fraction) {
Ok(next_fraction) => next_fraction,
Err(error) => ctx.state.fail(error, 0.0),
}
}
fn proxy_id_from_context(
proxy_id: i32,
proxies: *const HashMap<i32, ProxyEntry>,
owner: OwnerToken,
) -> Option<DynamicTreeProxyId> {
unsafe { proxies.as_ref() }
.and_then(|proxies| proxies.get(&proxy_id))
.map(|entry| DynamicTreeProxyId::new(proxy_id, owner, entry.resource))
}
fn aabb_contains(outer: Aabb, inner: Aabb) -> bool {
outer.lower_bound.x <= inner.lower_bound.x
&& outer.lower_bound.y <= inner.lower_bound.y
&& outer.lower_bound.z <= inner.lower_bound.z
&& inner.upper_bound.x <= outer.upper_bound.x
&& inner.upper_bound.y <= outer.upper_bound.y
&& inner.upper_bound.z <= outer.upper_bound.z
}
#[cfg(test)]
mod tests {
use super::*;
fn aabb(lower: f32, upper: f32) -> Aabb {
Aabb {
lower_bound: Vec3::new(lower, lower, lower),
upper_bound: Vec3::new(upper, upper, upper),
}
}
#[test]
fn recycled_native_proxy_slot_receives_a_new_resource_token() -> Result<()> {
let mut tree = DynamicTree::new()?;
let first = tree.create_proxy(aabb(-1.0, 1.0), 1)?;
tree.destroy_proxy(first)?;
let replacement = tree.create_proxy(aabb(-1.0, 1.0), 2)?;
assert_eq!(replacement.index, first.index);
assert_ne!(replacement.resource, first.resource);
assert_eq!(
tree.proxy(first),
Err(Error::StaleHandle {
kind: HandleKind::DynamicTreeProxy,
})
);
assert_eq!(tree.proxy(replacement)?.user_data, 2);
Ok(())
}
#[test]
fn invalid_cast_clip_reports_precise_validation_error() {
assert_eq!(
DynamicTreeCastControl::Clip(f32::NAN).into_raw(1.0),
Err(Error::InvalidValue {
context: "dynamic_tree.cast.clip_fraction",
reason: InvalidValueReason::NonFinite,
})
);
assert_eq!(
DynamicTreeCastControl::Clip(2.0).into_raw(1.0),
Err(Error::InvalidValue {
context: "dynamic_tree.cast.clip_fraction",
reason: InvalidValueReason::OutOfRange,
})
);
}
#[test]
fn excessive_capacity_is_a_caller_input_error() {
assert!(matches!(
DynamicTree::with_capacity(usize::MAX),
Err(Error::InvalidValue {
context: "dynamic_tree.proxy_capacity",
reason: InvalidValueReason::OutOfRange,
})
));
}
}