use std::cell::Cell;
use std::ffi::c_void;
use std::mem::MaybeUninit;
use std::ptr::{null, NonNull};
use oxijolt_sys::*;
use crate::body::with_read_locked_body;
use crate::filter::{with_query_filters, FilterState};
use crate::limits;
use crate::shape::compound_sub_shape_of;
use crate::{
BodyError, BodyId, CompoundSubShape, ObjectLayer, PhysicsWorld, Quat, QueryError, QueryFilter,
RVec3, Real, Shape, SubShapeId, Vec3,
};
mod cast;
mod collide;
mod point;
pub use cast::{ShapeCast, ShapeCastHit};
pub use collide::{CollideShape, CollideShapeHit};
pub use point::CollidePointHit;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RayCast {
origin: RVec3,
direction: Vec3,
}
impl RayCast {
pub fn new(origin: RVec3, direction: Vec3) -> Self {
Self { origin, direction }
}
pub fn origin(&self) -> RVec3 {
self.origin
}
pub fn direction(&self) -> Vec3 {
self.direction
}
pub fn point_at(&self, fraction: f32) -> RVec3 {
RVec3::new(
self.origin.x + Real::from(self.direction.x * fraction),
self.origin.y + Real::from(self.direction.y * fraction),
self.origin.z + Real::from(self.direction.z * fraction),
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct RayCastHit {
pub body: BodyId,
pub fraction: f32,
pub sub_shape_id: SubShapeId,
pub distance: f32,
pub normal: Vec3,
pub object_layer: ObjectLayer,
pub compound_child: Option<CompoundSubShape>,
}
pub(crate) struct ResultSlot<'s, T> {
state: &'s FilterState<'s>,
pub(crate) hit: Cell<Option<T>>,
}
impl<'s, T: Copy> ResultSlot<'s, T> {
pub(crate) fn new(state: &'s FilterState<'s>) -> Self {
Self {
state,
hit: Cell::new(None),
}
}
pub(crate) fn as_user_data(&self) -> *mut c_void {
(self as *const Self).cast_mut().cast()
}
pub(crate) unsafe fn store(user_data: *mut c_void, result: *const T) {
let slot = unsafe { &*user_data.cast::<Self>() };
let result = unsafe { *result };
slot.state.guarded((), || slot.hit.set(Some(result)));
}
}
unsafe extern "C" fn store_ray_hit(user_data: *mut c_void, result: *const JPH_RayCastResult) {
unsafe { ResultSlot::<JPH_RayCastResult>::store(user_data, result) }
}
impl PhysicsWorld {
pub fn cast_ray(
&self,
ray: &RayCast,
filter: &QueryFilter<'_>,
) -> Result<Option<RayCastHit>, QueryError> {
if !limits::is_in_frame(ray.origin) {
return Err(QueryError::InvalidValue(
"ray origin must be finite and within limits::MAX_POSITION",
));
}
if !(ray.direction.is_finite() && ray.direction != Vec3::ZERO) {
return Err(QueryError::InvalidValue(
"ray direction must be finite and not zero",
));
}
filter.validate(self)?;
let origin = ray.origin.to_jph();
let direction = ray.direction.to_jph();
let settings = JPH_RayCastSettings {
backFaceModeTriangles: JPH_BackFaceMode_CollideWithBackFaces,
backFaceModeConvex: JPH_BackFaceMode_IgnoreBackFaces,
treatConvexAsSolid: true,
};
let hit = with_query_filters(self, filter, |raw, state| {
let slot = ResultSlot::<JPH_RayCastResult>::new(state);
unsafe {
JPH_NarrowPhaseQuery_CastRay3(
self.narrow_phase_query.as_ptr(),
&origin,
&direction,
&settings,
JPH_CollisionCollectorType_ClosestHit,
Some(store_ray_hit),
slot.as_user_data(),
null(),
raw.object_layer,
raw.body,
raw.shape,
)
};
slot.hit.get()
})?;
let Some(hit) = hit else {
return Ok(None);
};
let body = BodyId::new(hit.bodyID, self.tag);
let point = ray.point_at(hit.fraction).to_jph();
let (normal, object_layer) =
with_read_locked_body(self.body_lock_interface, body, |locked| {
let mut normal = Vec3::ZERO.to_jph();
unsafe {
JPH_Body_GetWorldSpaceSurfaceNormal(
locked.as_ptr(),
hit.subShapeID2,
&point,
&mut normal,
);
}
let layer = unsafe { JPH_Body_GetObjectLayer(locked.as_ptr()) };
(Vec3::from_jph(normal), ObjectLayer::new(layer))
})
.ok_or(QueryError::AllocationFailed)?;
let sub_shape_id = SubShapeId::new(hit.subShapeID2);
Ok(Some(RayCastHit {
body,
fraction: hit.fraction,
sub_shape_id,
distance: hit.fraction * ray.direction.length(),
normal,
object_layer,
compound_child: self.compound_sub_shape(body, sub_shape_id).ok().flatten(),
}))
}
pub fn compound_sub_shape(
&self,
body: BodyId,
id: SubShapeId,
) -> Result<Option<CompoundSubShape>, BodyError> {
self.check(body)?;
let shape =
unsafe { JPH_BodyInterface_GetShape(self.body_interface.as_ptr(), body.to_raw()) };
let Some(shape) = NonNull::new(shape.cast_mut()) else {
return Ok(None);
};
Ok(unsafe { compound_sub_shape_of(shape, id) })
}
pub(crate) fn object_layer_of(&self, body: BodyId) -> ObjectLayer {
ObjectLayer::new(unsafe {
JPH_BodyInterface_GetObjectLayer(self.body_interface.as_ptr(), body.to_raw())
})
}
}
pub(crate) fn offset_from(base: RVec3, offset: JPH_Vec3) -> RVec3 {
RVec3::new(
base.x + Real::from(offset.x),
base.y + Real::from(offset.y),
base.z + Real::from(offset.z),
)
}
pub(crate) fn rotation_translation(rotation: Quat, position: RVec3) -> JPH_RMat4 {
let rotation = rotation.to_jph();
let position = position.to_jph();
let mut matrix = MaybeUninit::<JPH_RMat4>::uninit();
unsafe {
JPH_RMat4_RotationTranslation(matrix.as_mut_ptr(), &rotation, &position);
matrix.assume_init()
}
}
pub(crate) fn validate_pose(
shape: &Shape,
position: RVec3,
rotation: Quat,
) -> Result<(), QueryError> {
if !limits::is_in_frame(position) {
return Err(QueryError::InvalidValue(
"query shape position must be finite and within limits::MAX_POSITION",
));
}
if !rotation.is_valid_rotation() {
return Err(QueryError::InvalidValue(
"query shape rotation must be a finite unit quaternion",
));
}
if shape.must_be_static() {
return Err(QueryError::InvalidValue(
"static-only shapes cannot be query shapes",
));
}
Ok(())
}