use std::cell::RefCell;
use std::ffi::c_void;
use std::fmt;
use std::mem::MaybeUninit;
use std::ptr::null;
use oxijolt_sys::*;
use super::{offset_from, rotation_translation, validate_pose};
use crate::filter::{with_query_filters, FilterState};
use crate::limits;
use crate::{
BodyId, CompoundSubShape, ObjectLayer, PhysicsWorld, Quat, QueryError, QueryFilter, RVec3,
Shape, SubShapeId, Vec3,
};
#[derive(Clone, Copy)]
pub struct CollideShape<'a> {
shape: &'a Shape,
position: RVec3,
rotation: Quat,
max_separation_distance: f32,
back_faces: bool,
}
impl fmt::Debug for CollideShape<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CollideShape")
.field("position", &self.position)
.field("rotation", &self.rotation)
.field("max_separation_distance", &self.max_separation_distance)
.field("back_faces", &self.back_faces)
.finish_non_exhaustive()
}
}
impl<'a> CollideShape<'a> {
pub fn new(shape: &'a Shape, position: RVec3, rotation: Quat) -> Self {
Self {
shape,
position,
rotation,
max_separation_distance: 0.0,
back_faces: false,
}
}
#[must_use]
pub fn max_separation_distance(mut self, metres: f32) -> Self {
self.max_separation_distance = metres;
self
}
#[must_use]
pub fn collide_with_back_faces(mut self, value: bool) -> Self {
self.back_faces = value;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct CollideShapeHit {
pub body: BodyId,
pub sub_shape_id: SubShapeId,
pub object_layer: ObjectLayer,
pub compound_child: Option<CompoundSubShape>,
pub point_on_shape: RVec3,
pub point_on_body: RVec3,
pub normal: Vec3,
pub penetration_depth: f32,
}
#[derive(Clone, Copy)]
struct RawCollideHit {
point_on_1: JPH_Vec3,
point_on_2: JPH_Vec3,
axis: JPH_Vec3,
depth: f32,
sub_shape_id2: JPH_SubShapeID,
body: JPH_BodyID,
}
struct CollideSlot<'s> {
state: &'s FilterState<'s>,
hits: RefCell<Vec<RawCollideHit>>,
}
unsafe extern "C" fn push_collide_hit(
user_data: *mut c_void,
result: *const JPH_CollideShapeResult,
) {
let slot = unsafe { &*user_data.cast::<CollideSlot<'_>>() };
let result = unsafe { &*result };
let hit = RawCollideHit {
point_on_1: result.contactPointOn1,
point_on_2: result.contactPointOn2,
axis: result.penetrationAxis,
depth: result.penetrationDepth,
sub_shape_id2: result.subShapeID2,
body: result.bodyID2,
};
slot.state.guarded((), || slot.hits.borrow_mut().push(hit));
}
fn default_collide_settings() -> JPH_CollideShapeSettings {
let mut settings = MaybeUninit::<JPH_CollideShapeSettings>::uninit();
unsafe {
JPH_CollideShapeSettings_Init(settings.as_mut_ptr());
settings.assume_init()
}
}
impl PhysicsWorld {
pub fn collide_shape(
&self,
query: &CollideShape<'_>,
filter: &QueryFilter<'_>,
) -> Result<Vec<CollideShapeHit>, QueryError> {
validate_pose(query.shape, query.position, query.rotation)?;
if !limits::is_local_distance(query.max_separation_distance) {
return Err(QueryError::InvalidValue(
"max separation distance must be between 0 and limits::MAX_SHAPE_EXTENT",
));
}
filter.validate(self)?;
let mut settings = default_collide_settings();
debug_assert_eq!(settings.base.collectFacesMode, JPH_CollectFacesMode_NoFaces);
settings.maxSeparationDistance = query.max_separation_distance;
settings.backFaceMode = if query.back_faces {
JPH_BackFaceMode_CollideWithBackFaces
} else {
JPH_BackFaceMode_IgnoreBackFaces
};
let center_of_mass = query.rotation.rotate(query.shape.center_of_mass());
let transform = rotation_translation(
query.rotation,
offset_from(query.position, center_of_mass.to_jph()),
);
let scale = Vec3::new(1.0, 1.0, 1.0).to_jph();
let mut base_offset = query.position.to_jph();
let hits = with_query_filters(self, filter, |raw, state| {
let slot = CollideSlot {
state,
hits: RefCell::new(Vec::new()),
};
unsafe {
JPH_NarrowPhaseQuery_CollideShape2(
self.narrow_phase_query.as_ptr(),
query.shape.as_ptr(),
&scale,
&transform,
&settings,
&mut base_offset,
JPH_CollisionCollectorType_AllHit,
Some(push_collide_hit),
(&slot as *const CollideSlot<'_>).cast_mut().cast(),
null(),
raw.object_layer,
raw.body,
raw.shape,
)
};
slot.hits.into_inner()
})?;
Ok(hits
.into_iter()
.map(|hit| {
let body = BodyId::new(hit.body, self.tag);
let sub_shape_id = SubShapeId::new(hit.sub_shape_id2);
CollideShapeHit {
body,
sub_shape_id,
object_layer: self.object_layer_of(body),
compound_child: self.compound_sub_shape(body, sub_shape_id).ok().flatten(),
point_on_shape: offset_from(query.position, hit.point_on_1),
point_on_body: offset_from(query.position, hit.point_on_2),
normal: Vec3::from_jph(hit.axis).normalized_or_zero().scale(-1.0),
penetration_depth: hit.depth,
}
})
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn joltc_collide_settings_init_gives_jolts_defaults() {
let settings = default_collide_settings();
assert_eq!(settings.base.collectFacesMode, JPH_CollectFacesMode_NoFaces);
assert_eq!(
settings.base.activeEdgeMode,
JPH_ActiveEdgeMode_CollideOnlyWithActive
);
assert_eq!(settings.base.collisionTolerance, 1.0e-4);
assert_eq!(settings.base.penetrationTolerance, 1.0e-4);
assert_eq!(settings.backFaceMode, JPH_BackFaceMode_IgnoreBackFaces);
assert_eq!(settings.maxSeparationDistance, 0.0);
}
}