#![cfg_attr(all(target_arch = "wasm32", boxddd_wasm_provider), allow(dead_code))]
use crate::collision::{ShapeCastInput, ShapeProxy};
use crate::core::{box3d_lock, callback_state};
use crate::error::{Error, Result};
use crate::shapes::Capsule;
use crate::types::{Aabb, Plane, Pos, ShapeId, Vec3};
use crate::world::World;
use boxddd_sys::ffi;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
use std::cell::RefCell;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
use std::collections::HashMap;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, catch_unwind};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TreeStats {
pub node_visits: i32,
pub leaf_visits: i32,
}
impl TreeStats {
#[inline]
pub const fn from_raw(raw: ffi::b3TreeStats) -> Self {
Self {
node_visits: raw.nodeVisits,
leaf_visits: raw.leafVisits,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct QueryFilter {
pub category_bits: u64,
pub mask_bits: u64,
pub id: u64,
}
impl QueryFilter {
pub fn new() -> Self {
Self::default()
}
pub fn category_bits(mut self, category_bits: u64) -> Self {
self.category_bits = category_bits;
self
}
pub fn mask_bits(mut self, mask_bits: u64) -> Self {
self.mask_bits = mask_bits;
self
}
pub fn id(mut self, id: u64) -> Self {
self.id = id;
self
}
#[inline]
pub(crate) fn raw(self) -> ffi::b3QueryFilter {
ffi::b3QueryFilter {
categoryBits: self.category_bits,
maskBits: self.mask_bits,
id: self.id,
name: std::ptr::null(),
}
}
}
impl Default for QueryFilter {
fn default() -> Self {
Self {
category_bits: u64::MAX,
mask_bits: u64::MAX,
id: 0,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct QueryHit {
pub shape_id: ShapeId,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct RayHit {
pub shape_id: ShapeId,
pub point: Pos,
pub normal: Vec3,
pub fraction: f32,
pub user_material_id: u64,
pub triangle_index: i32,
pub child_index: i32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct RayClosestHit {
pub shape_id: ShapeId,
pub point: Pos,
pub normal: Vec3,
pub fraction: f32,
pub user_material_id: u64,
pub triangle_index: i32,
pub child_index: i32,
pub node_visits: i32,
pub leaf_visits: i32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct BodyClosestPoint {
pub point: Vec3,
pub distance: f32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct BodyCastHit {
pub shape_id: ShapeId,
pub point: Pos,
pub normal: Vec3,
pub fraction: f32,
pub user_material_id: u64,
pub triangle_index: i32,
pub iterations: i32,
}
impl BodyCastHit {
#[inline]
pub(crate) fn from_raw(raw: ffi::b3BodyCastResult) -> Option<Self> {
raw.hit.then(|| Self {
shape_id: ShapeId::from_raw(raw.shapeId),
point: Pos::from_raw(raw.point),
normal: Vec3::from_raw(raw.normal),
fraction: raw.fraction,
user_material_id: raw.userMaterialId,
triangle_index: raw.triangleIndex,
iterations: raw.iterations,
})
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ShapeRayHit {
pub point: Pos,
pub normal: Vec3,
pub fraction: f32,
pub triangle_index: i32,
pub child_index: i32,
pub material_index: i32,
pub iterations: i32,
}
impl ShapeRayHit {
#[inline]
pub(crate) fn from_raw(raw: ffi::b3WorldCastOutput) -> Option<Self> {
raw.hit.then(|| Self {
point: Pos::from_raw(raw.point),
normal: Vec3::from_raw(raw.normal),
fraction: raw.fraction,
triangle_index: raw.triangleIndex,
child_index: raw.childIndex,
material_index: raw.materialIndex,
iterations: raw.iterations,
})
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MoverPlane {
pub shape_id: ShapeId,
pub plane: Plane,
pub point: Vec3,
}
impl MoverPlane {
#[inline]
pub(crate) fn from_raw(shape_id: ffi::b3ShapeId, raw: ffi::b3PlaneResult) -> Self {
Self {
shape_id: ShapeId::from_raw(shape_id),
plane: Plane::from_raw(raw.plane),
point: Vec3::from_raw(raw.point),
}
}
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
thread_local! {
static PROVIDER_QUERY: RefCell<ProviderQueryRegistry> = RefCell::new(ProviderQueryRegistry::default());
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
#[derive(Default)]
struct ProviderQueryRegistry {
calls: HashMap<u32, ProviderQueryEntry>,
next_token: u32,
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
struct ProviderQueryEntry {
call: ProviderQueryCall,
first_error: Option<Error>,
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
#[derive(Copy, Clone)]
struct ProviderQueryCall {
kind: ProviderQueryKind,
context: *mut c_void,
overlap: Option<unsafe fn(*mut c_void, ffi::b3ShapeId) -> bool>,
cast: Option<
unsafe fn(*mut c_void, ffi::b3ShapeId, ffi::b3Pos, ffi::b3Vec3, f32, u64, i32, i32) -> f32,
>,
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
#[derive(Copy, Clone, Eq, PartialEq)]
enum ProviderQueryKind {
Overlap,
Cast,
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
impl ProviderQueryRegistry {
fn allocate_token_id(&mut self) -> Option<u32> {
self.next_token = self.next_token.checked_add(1)?;
Some(self.next_token)
}
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
struct ProviderQueryGuard {
token: u32,
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
impl ProviderQueryGuard {
fn overlap<F>(visitor: &mut F) -> Result<Self>
where
F: FnMut(ShapeId) -> bool,
{
register_provider_query(ProviderQueryCall {
kind: ProviderQueryKind::Overlap,
context: (visitor as *mut F).cast::<c_void>(),
overlap: Some(provider_overlap_dispatch::<F>),
cast: None,
})
}
fn cast<F>(visitor: &mut F) -> Result<Self>
where
F: FnMut(RayHit) -> f32,
{
register_provider_query(ProviderQueryCall {
kind: ProviderQueryKind::Cast,
context: (visitor as *mut F).cast::<c_void>(),
overlap: None,
cast: Some(provider_cast_dispatch::<F>),
})
}
fn token(&self) -> u32 {
self.token
}
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
impl Drop for ProviderQueryGuard {
fn drop(&mut self) {
PROVIDER_QUERY.with(|state| {
state.borrow_mut().calls.remove(&self.token);
});
}
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
fn register_provider_query(call: ProviderQueryCall) -> Result<ProviderQueryGuard> {
PROVIDER_QUERY.with(|state| {
let mut state = state.borrow_mut();
let token = state
.allocate_token_id()
.ok_or(Error::CallbackSlotsExhausted)?;
state.calls.insert(
token,
ProviderQueryEntry {
call,
first_error: None,
},
);
Ok(ProviderQueryGuard { token })
})
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
fn provider_query_call(token: u32) -> Option<ProviderQueryCall> {
PROVIDER_QUERY.with(|state| state.borrow().calls.get(&token).map(|entry| entry.call))
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
fn provider_query_fail(token: u32, error: Error) {
if token == 0 {
return;
}
PROVIDER_QUERY.with(|state| {
let mut state = state.borrow_mut();
if let Some(entry) = state.calls.get_mut(&token) {
entry.first_error.get_or_insert(error);
}
});
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
fn take_provider_query_error(token: u32) -> Option<Error> {
if token == 0 {
return None;
}
PROVIDER_QUERY.with(|state| {
state
.borrow_mut()
.calls
.get_mut(&token)
.and_then(|entry| entry.first_error.take())
})
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
fn provider_query_bridge_error(token: u32) -> Option<Error> {
let provider_error = unsafe { ffi::boxddd_provider_query_take_error(token) };
if provider_error != 0 {
provider_query_fail(token, Error::ProviderCallbackFailed);
}
take_provider_query_error(token)
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
unsafe fn provider_read<T: Copy>(ptr: *const T) -> Option<T> {
unsafe { ptr.as_ref().copied() }
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
unsafe fn provider_overlap_dispatch<F>(context: *mut c_void, shape_id: ffi::b3ShapeId) -> bool
where
F: FnMut(ShapeId) -> bool,
{
let visitor = unsafe { &mut *context.cast::<F>() };
visitor(ShapeId::from_raw(shape_id))
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
unsafe fn provider_cast_dispatch<F>(
context: *mut c_void,
shape_id: ffi::b3ShapeId,
point: ffi::b3Pos,
normal: ffi::b3Vec3,
fraction: f32,
user_material_id: u64,
triangle_index: i32,
child_index: i32,
) -> f32
where
F: FnMut(RayHit) -> f32,
{
let visitor = unsafe { &mut *context.cast::<F>() };
visitor(RayHit {
shape_id: ShapeId::from_raw(shape_id),
point: Pos::from_raw(point),
normal: Vec3::from_raw(normal),
fraction,
user_material_id,
triangle_index,
child_index,
})
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
#[unsafe(no_mangle)]
pub extern "C" fn boxddd_query_overlap(token: u32, shape_id: *const ffi::b3ShapeId) -> i32 {
let Some(shape_id) = (unsafe { provider_read(shape_id) }) else {
provider_query_fail(token, Error::ProviderCallbackFailed);
return 0;
};
let Some(call) = provider_query_call(token) else {
provider_query_fail(token, Error::ProviderCallbackFailed);
return 0;
};
let Some(dispatch) = call
.overlap
.filter(|_| call.kind == ProviderQueryKind::Overlap)
else {
provider_query_fail(token, Error::ProviderCallbackFailed);
return 0;
};
let _guard = callback_state::CallbackGuard::enter();
match catch_unwind(AssertUnwindSafe(|| unsafe {
dispatch(call.context, shape_id)
})) {
Ok(keep_going) => i32::from(keep_going),
Err(_) => {
provider_query_fail(token, Error::CallbackPanicked);
0
}
}
}
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
#[unsafe(no_mangle)]
pub extern "C" fn boxddd_query_cast(
token: u32,
shape_id: *const ffi::b3ShapeId,
point: *const ffi::b3Pos,
normal: *const ffi::b3Vec3,
fraction: f32,
user_material_id: *const u64,
triangle_index: i32,
child_index: i32,
) -> f32 {
let (Some(shape_id), Some(point), Some(normal), Some(user_material_id)) = (
unsafe { provider_read(shape_id) },
unsafe { provider_read(point) },
unsafe { provider_read(normal) },
unsafe { provider_read(user_material_id) },
) else {
provider_query_fail(token, Error::ProviderCallbackFailed);
return 0.0;
};
let Some(call) = provider_query_call(token) else {
provider_query_fail(token, Error::ProviderCallbackFailed);
return 0.0;
};
let Some(dispatch) = call.cast.filter(|_| call.kind == ProviderQueryKind::Cast) else {
provider_query_fail(token, Error::ProviderCallbackFailed);
return 0.0;
};
let _guard = callback_state::CallbackGuard::enter();
match catch_unwind(AssertUnwindSafe(|| unsafe {
dispatch(
call.context,
shape_id,
point,
normal,
fraction,
user_material_id,
triangle_index,
child_index,
)
})) {
Ok(next_fraction) if next_fraction.is_finite() => next_fraction,
Ok(_) => 0.0,
Err(_) => {
provider_query_fail(token, Error::CallbackPanicked);
0.0
}
}
}
impl World {
pub fn overlap_aabb(&self, aabb: Aabb, filter: QueryFilter) -> Result<Vec<QueryHit>> {
let mut out = Vec::new();
self.overlap_aabb_into(aabb, filter, &mut out)?;
Ok(out)
}
pub fn overlap_aabb_into(
&self,
aabb: Aabb,
filter: QueryFilter,
out: &mut Vec<QueryHit>,
) -> Result<TreeStats> {
out.clear();
self.visit_overlap_aabb(aabb, filter, |shape_id| {
out.push(QueryHit { shape_id });
true
})
}
pub fn visit_overlap_aabb<F>(
&self,
aabb: Aabb,
filter: QueryFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(ShapeId) -> bool,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let aabb = aabb.validate()?;
let mut visitor = visitor;
let provider_query = ProviderQueryGuard::overlap(&mut visitor)?;
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let stats = unsafe {
ffi::boxddd_provider_world_overlap_aabb(
self.raw(),
aabb.into_raw(),
filter.raw(),
provider_query.token(),
)
};
if let Some(error) = provider_query_bridge_error(provider_query.token()) {
Err(error)
} else {
Ok(TreeStats::from_raw(stats))
}
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
aabb.validate()?;
let mut ctx = OverlapContext {
visitor,
panicked: false,
};
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let stats = unsafe {
ffi::b3World_OverlapAABB(
self.raw(),
aabb.into_raw(),
filter.raw(),
Some(overlap_trampoline::<F>),
(&mut ctx as *mut OverlapContext<_>).cast(),
)
};
if ctx.panicked {
Err(Error::CallbackPanicked)
} else {
Ok(TreeStats::from_raw(stats))
}
}
}
pub fn overlap_shape(
&self,
origin: impl Into<Pos>,
proxy: &ShapeProxy,
filter: QueryFilter,
) -> Result<Vec<QueryHit>> {
let mut out = Vec::new();
self.overlap_shape_into(origin, proxy, filter, &mut out)?;
Ok(out)
}
pub fn overlap_shape_into(
&self,
origin: impl Into<Pos>,
proxy: &ShapeProxy,
filter: QueryFilter,
out: &mut Vec<QueryHit>,
) -> Result<TreeStats> {
out.clear();
self.visit_overlap_shape(origin, proxy, filter, |shape_id| {
out.push(QueryHit { shape_id });
true
})
}
pub fn visit_overlap_shape<F>(
&self,
origin: impl Into<Pos>,
proxy: &ShapeProxy,
filter: QueryFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(ShapeId) -> bool,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (origin, proxy, filter, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let origin = origin.into().validate()?;
let raw_proxy = proxy.raw();
let mut ctx = OverlapContext {
visitor,
panicked: false,
};
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let stats = unsafe {
ffi::b3World_OverlapShape(
self.raw(),
origin.into_raw(),
&raw_proxy,
filter.raw(),
Some(overlap_trampoline::<F>),
(&mut ctx as *mut OverlapContext<_>).cast(),
)
};
if ctx.panicked {
Err(Error::CallbackPanicked)
} else {
Ok(TreeStats::from_raw(stats))
}
}
}
pub fn cast_ray(
&self,
origin: impl Into<Pos>,
translation: impl Into<Vec3>,
filter: QueryFilter,
) -> Result<Vec<RayHit>> {
let mut out = Vec::new();
self.cast_ray_into(origin, translation, filter, &mut out)?;
Ok(out)
}
pub fn cast_ray_into(
&self,
origin: impl Into<Pos>,
translation: impl Into<Vec3>,
filter: QueryFilter,
out: &mut Vec<RayHit>,
) -> Result<TreeStats> {
out.clear();
self.visit_cast_ray(origin, translation, filter, |hit| {
out.push(hit);
1.0
})
}
pub fn visit_cast_ray<F>(
&self,
origin: impl Into<Pos>,
translation: impl Into<Vec3>,
filter: QueryFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(RayHit) -> f32,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let origin = origin.into().validate()?;
let translation = translation.into().validate()?;
let mut visitor = visitor;
let provider_query = ProviderQueryGuard::cast(&mut visitor)?;
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let stats = unsafe {
ffi::boxddd_provider_world_cast_ray(
self.raw(),
origin.into_raw(),
translation.into_raw(),
filter.raw(),
provider_query.token(),
)
};
if let Some(error) = provider_query_bridge_error(provider_query.token()) {
Err(error)
} else {
Ok(TreeStats::from_raw(stats))
}
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let origin = origin.into().validate()?;
let translation = translation.into().validate()?;
let mut ctx = CastContext {
visitor,
panicked: false,
};
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let stats = unsafe {
ffi::b3World_CastRay(
self.raw(),
origin.into_raw(),
translation.into_raw(),
filter.raw(),
Some(cast_trampoline::<F>),
(&mut ctx as *mut CastContext<_>).cast(),
)
};
if ctx.panicked {
Err(Error::CallbackPanicked)
} else {
Ok(TreeStats::from_raw(stats))
}
}
}
pub fn cast_ray_closest(
&self,
origin: impl Into<Pos>,
translation: impl Into<Vec3>,
filter: QueryFilter,
) -> Result<Option<RayClosestHit>> {
callback_state::check_not_in_callback()?;
let origin = origin.into().validate()?;
let translation = translation.into().validate()?;
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let raw = unsafe {
ffi::b3World_CastRayClosest(
self.raw(),
origin.into_raw(),
translation.into_raw(),
filter.raw(),
)
};
if raw.hit {
Ok(Some(RayClosestHit {
shape_id: ShapeId::from_raw(raw.shapeId),
point: Pos::from_raw(raw.point),
normal: Vec3::from_raw(raw.normal),
fraction: raw.fraction,
user_material_id: raw.userMaterialId,
triangle_index: raw.triangleIndex,
child_index: raw.childIndex,
node_visits: raw.nodeVisits,
leaf_visits: raw.leafVisits,
}))
} else {
Ok(None)
}
}
pub fn cast_shape(
&self,
origin: impl Into<Pos>,
input: ShapeCastInput,
filter: QueryFilter,
) -> Result<Vec<RayHit>> {
let mut out = Vec::new();
self.cast_shape_into(origin, input, filter, &mut out)?;
Ok(out)
}
pub fn cast_shape_into(
&self,
origin: impl Into<Pos>,
input: ShapeCastInput,
filter: QueryFilter,
out: &mut Vec<RayHit>,
) -> Result<TreeStats> {
out.clear();
self.visit_cast_shape(origin, input, filter, |hit| {
out.push(hit);
1.0
})
}
pub fn visit_cast_shape<F>(
&self,
origin: impl Into<Pos>,
input: ShapeCastInput,
filter: QueryFilter,
visitor: F,
) -> Result<TreeStats>
where
F: FnMut(RayHit) -> f32,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (origin, input, filter, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let origin = origin.into().validate()?;
let input = input.validate()?;
let raw_input = input.raw();
let mut ctx = CastContext {
visitor,
panicked: false,
};
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
let stats = unsafe {
ffi::b3World_CastShape(
self.raw(),
origin.into_raw(),
&raw_input.proxy,
raw_input.translation,
filter.raw(),
Some(cast_trampoline::<F>),
(&mut ctx as *mut CastContext<_>).cast(),
)
};
if ctx.panicked {
Err(Error::CallbackPanicked)
} else {
Ok(TreeStats::from_raw(stats))
}
}
}
pub fn cast_mover(
&self,
origin: impl Into<Pos>,
mover: &Capsule,
translation: impl Into<Vec3>,
filter: QueryFilter,
) -> Result<f32> {
callback_state::check_not_in_callback()?;
let origin = origin.into().validate()?;
let translation = translation.into().validate()?;
mover.validate()?;
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
Ok(unsafe {
ffi::b3World_CastMover(
self.raw(),
origin.into_raw(),
mover.raw(),
translation.into_raw(),
filter.raw(),
None,
std::ptr::null_mut(),
)
})
}
pub fn collide_mover(
&self,
origin: impl Into<Pos>,
mover: &Capsule,
filter: QueryFilter,
) -> Result<Vec<MoverPlane>> {
let mut out = Vec::new();
self.collide_mover_into(origin, mover, filter, &mut out)?;
Ok(out)
}
pub fn collide_mover_into(
&self,
origin: impl Into<Pos>,
mover: &Capsule,
filter: QueryFilter,
out: &mut Vec<MoverPlane>,
) -> Result<()> {
out.clear();
self.visit_collide_mover(origin, mover, filter, |plane| {
out.push(plane);
true
})
}
pub fn visit_collide_mover<F>(
&self,
origin: impl Into<Pos>,
mover: &Capsule,
filter: QueryFilter,
visitor: F,
) -> Result<()>
where
F: FnMut(MoverPlane) -> bool,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (origin, mover, filter, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let origin = origin.into().validate()?;
mover.validate()?;
let mut ctx = MoverPlaneContext {
visitor,
panicked: false,
};
let _guard = box3d_lock::lock();
self.check_world_valid_locked()?;
unsafe {
ffi::b3World_CollideMover(
self.raw(),
origin.into_raw(),
mover.raw(),
filter.raw(),
Some(mover_plane_trampoline::<F>),
(&mut ctx as *mut MoverPlaneContext<_>).cast(),
)
};
if ctx.panicked {
Err(Error::CallbackPanicked)
} else {
Ok(())
}
}
}
}
struct OverlapContext<F> {
visitor: F,
panicked: bool,
}
unsafe extern "C" fn overlap_trampoline<F>(
shape_id: ffi::b3ShapeId,
context: *mut std::ffi::c_void,
) -> bool
where
F: FnMut(ShapeId) -> bool,
{
let _guard = callback_state::CallbackGuard::enter();
let ctx = unsafe { &mut *context.cast::<OverlapContext<F>>() };
match catch_unwind(AssertUnwindSafe(|| {
(ctx.visitor)(ShapeId::from_raw(shape_id))
})) {
Ok(keep_going) => keep_going,
Err(_) => {
ctx.panicked = true;
false
}
}
}
struct CastContext<F> {
visitor: F,
panicked: bool,
}
unsafe extern "C" fn cast_trampoline<F>(
shape_id: ffi::b3ShapeId,
point: ffi::b3Pos,
normal: ffi::b3Vec3,
fraction: f32,
user_material_id: u64,
triangle_index: i32,
child_index: i32,
context: *mut std::ffi::c_void,
) -> f32
where
F: FnMut(RayHit) -> f32,
{
let _guard = callback_state::CallbackGuard::enter();
let ctx = unsafe { &mut *context.cast::<CastContext<F>>() };
let hit = RayHit {
shape_id: ShapeId::from_raw(shape_id),
point: Pos::from_raw(point),
normal: Vec3::from_raw(normal),
fraction,
user_material_id,
triangle_index,
child_index,
};
match catch_unwind(AssertUnwindSafe(|| (ctx.visitor)(hit))) {
Ok(next_fraction) if next_fraction.is_finite() => next_fraction,
Ok(_) => 0.0,
Err(_) => {
ctx.panicked = true;
0.0
}
}
}
struct MoverPlaneContext<F> {
visitor: F,
panicked: bool,
}
unsafe extern "C" fn mover_plane_trampoline<F>(
shape_id: ffi::b3ShapeId,
plane: *const ffi::b3PlaneResult,
plane_count: i32,
context: *mut std::ffi::c_void,
) -> bool
where
F: FnMut(MoverPlane) -> bool,
{
let _guard = callback_state::CallbackGuard::enter();
let ctx = unsafe { &mut *context.cast::<MoverPlaneContext<F>>() };
if plane.is_null() || plane_count <= 0 {
return true;
}
let planes = unsafe { std::slice::from_raw_parts(plane, plane_count as usize) };
for raw_plane in planes {
let mover_plane = MoverPlane::from_raw(shape_id, *raw_plane);
match catch_unwind(AssertUnwindSafe(|| (ctx.visitor)(mover_plane))) {
Ok(true) => {}
Ok(false) => return false,
Err(_) => {
ctx.panicked = true;
return false;
}
}
}
true
}