use std::any::Any;
use std::cell::{Cell, RefCell};
use std::ffi::c_void;
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::ptr::null;
use oxijolt_sys::*;
use crate::filter::{with_query_filters, FilterState};
use crate::limits;
use crate::{
BodyId, CompoundSubShape, ObjectLayer, PhysicsWorld, QueryError, QueryFilter, RVec3, Shape,
SubShapeId, Vec3,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct CollidePointHit {
pub body: BodyId,
pub sub_shape_id: SubShapeId,
pub object_layer: ObjectLayer,
pub compound_child: Option<CompoundSubShape>,
}
struct WorldPointSlot<'s> {
state: &'s FilterState<'s>,
hits: RefCell<Vec<(JPH_BodyID, JPH_SubShapeID)>>,
}
unsafe extern "C" fn push_world_point_hit(
user_data: *mut c_void,
result: *const JPH_CollidePointResult,
) {
let slot = unsafe { &*user_data.cast::<WorldPointSlot<'_>>() };
let result = unsafe { *result };
slot.state.guarded((), || {
slot.hits
.borrow_mut()
.push((result.bodyID, result.subShapeID2));
});
}
#[derive(Default)]
struct ShapePointSlot {
ids: RefCell<Vec<JPH_SubShapeID>>,
panic: Cell<Option<Box<dyn Any + Send>>>,
}
unsafe extern "C" fn push_shape_point_hit(
user_data: *mut c_void,
result: *const JPH_CollidePointResult,
) {
let slot = unsafe { &*user_data.cast::<ShapePointSlot>() };
let id = unsafe { (*result).subShapeID2 };
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| slot.ids.borrow_mut().push(id))) {
slot.panic.set(Some(payload));
}
}
impl PhysicsWorld {
pub fn collide_point(
&self,
point: RVec3,
filter: &QueryFilter<'_>,
) -> Result<Vec<CollidePointHit>, QueryError> {
if !limits::is_in_frame(point) {
return Err(QueryError::InvalidValue(
"point must be finite and within limits::MAX_POSITION",
));
}
filter.validate(self)?;
let point = point.to_jph();
let mut hits = with_query_filters(self, filter, |raw, state| {
let slot = WorldPointSlot {
state,
hits: RefCell::new(Vec::new()),
};
unsafe {
JPH_NarrowPhaseQuery_CollidePoint2(
self.narrow_phase_query.as_ptr(),
&point,
JPH_CollisionCollectorType_AllHit,
Some(push_world_point_hit),
(&slot as *const WorldPointSlot<'_>).cast_mut().cast(),
null(),
raw.object_layer,
raw.body,
raw.shape,
)
};
slot.hits.into_inner()
})?;
hits.sort_unstable();
Ok(hits
.into_iter()
.map(|(body, id)| {
let body = BodyId::new(body, self.tag);
let sub_shape_id = SubShapeId::new(id);
CollidePointHit {
body,
sub_shape_id,
object_layer: self.object_layer_of(body),
compound_child: self.compound_sub_shape(body, sub_shape_id).ok().flatten(),
}
})
.collect())
}
}
impl Shape {
pub fn collide_point(&self, point: Vec3) -> Result<Vec<SubShapeId>, QueryError> {
if !limits::is_local_offset(point) {
return Err(QueryError::InvalidValue(
"point must be finite and within limits::MAX_SHAPE_EXTENT",
));
}
let center = self.center_of_mass();
let local = Vec3::new(point.x - center.x, point.y - center.y, point.z - center.z).to_jph();
let slot = ShapePointSlot::default();
unsafe {
JPH_Shape_CollidePoint2(
self.as_ptr(),
&local,
JPH_CollisionCollectorType_AllHit,
Some(push_shape_point_hit),
(&slot as *const ShapePointSlot).cast_mut().cast(),
null(),
)
};
if let Some(payload) = slot.panic.take() {
resume_unwind(payload);
}
let mut ids: Vec<SubShapeId> = slot
.ids
.into_inner()
.into_iter()
.map(SubShapeId::new)
.collect();
ids.sort_unstable();
Ok(ids)
}
}