use std::fmt;
use oxijolt_sys::*;
use crate::body::MotionType;
use crate::owned::{JoltObject, Owned};
use crate::world::WorldTag;
use crate::{BodyId, ObjectLayer, RVec3, Shape, SubShapeId, Vec3};
mod handle;
mod listener;
mod settings;
mod update;
pub use handle::{CharacterMut, CharacterRef};
pub use listener::{
BodyVelocity, CharacterContactKey, CharacterContactListener, CharacterContactSettings,
};
pub use settings::{CharacterSettings, ExtendedUpdateSettings};
pub(crate) const INNER_BODY_INERTIA_RULE: &str =
"inner body shape must give a finite inverse mass and a well-conditioned inertia";
const INVALID_ID: u32 = 0xffff_ffff;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CharacterId {
raw: u32,
pub(crate) world: WorldTag,
}
impl CharacterId {
pub(crate) fn new(raw: u32, world: WorldTag) -> Self {
Self { raw, world }
}
pub fn to_raw(self) -> u32 {
self.raw
}
}
impl fmt::Debug for CharacterId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("CharacterId").field(&self.raw).finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum GroundState {
OnGround,
OnSteepGround,
NotSupported,
InAir,
}
impl GroundState {
fn from_jph(value: JPH_GroundState) -> Self {
[
(JPH_GroundState_OnGround, Self::OnGround),
(JPH_GroundState_OnSteepGround, Self::OnSteepGround),
(JPH_GroundState_NotSupported, Self::NotSupported),
(JPH_GroundState_InAir, Self::InAir),
]
.into_iter()
.find_map(|(raw, state)| (raw == value).then_some(state))
.unwrap_or_else(|| unreachable!("Jolt returned unknown ground state {value}"))
}
pub fn is_supported(self) -> bool {
matches!(self, Self::OnGround | Self::OnSteepGround)
}
}
#[derive(Clone, Copy)]
pub struct InnerBody<'a> {
pub shape: &'a Shape,
pub object_layer: ObjectLayer,
}
impl fmt::Debug for InnerBody<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InnerBody")
.field("object_layer", &self.object_layer)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct CharacterContact {
pub body: Option<BodyId>,
pub character: Option<CharacterId>,
pub sub_shape_id: SubShapeId,
pub position: RVec3,
pub contact_normal: Vec3,
pub surface_normal: Vec3,
pub distance: f32,
pub fraction: f32,
pub linear_velocity: Vec3,
pub motion_type: MotionType,
pub is_sensor: bool,
pub had_collision: bool,
pub was_discarded: bool,
pub is_back_facing: bool,
}
impl CharacterContact {
fn from_jph(contact: &JPH_CharacterContact, world: WorldTag) -> Self {
Self {
body: (contact.bodyB != INVALID_ID).then(|| BodyId::new(contact.bodyB, world)),
character: (contact.characterIDB != INVALID_ID)
.then(|| CharacterId::new(contact.characterIDB, world)),
sub_shape_id: SubShapeId::new(contact.subShapeIDB),
position: RVec3::from_jph(contact.position),
contact_normal: Vec3::from_jph(contact.contactNormal),
surface_normal: Vec3::from_jph(contact.surfaceNormal),
distance: contact.distance,
fraction: contact.fraction,
linear_velocity: Vec3::from_jph(contact.linearVelocity),
motion_type: MotionType::from_jph(contact.motionTypeB),
is_sensor: contact.isSensorB,
had_collision: contact.hadCollision,
was_discarded: contact.wasDiscarded,
is_back_facing: contact.isBackFacingContact,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CharacterState {
jolt: Vec<u8>,
up: [u32; 3],
}
impl CharacterState {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.jolt.clone();
for bits in self.up {
bytes.extend_from_slice(&bits.to_le_bytes());
}
bytes
}
}
impl JoltObject for JPH_StateRecorder {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_StateRecorder_Destroy(ptr) };
}
}
impl JoltObject for JPH_CharacterVirtual {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_CharacterBase_Destroy(ptr.cast()) };
}
}
impl JoltObject for JPH_CharacterVsCharacterCollision {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_CharacterVsCharacterCollision_Destroy(ptr) };
}
}
pub(crate) struct CharacterEntry {
character: Owned<JPH_CharacterVirtual>,
contact_materials: Vec<Owned<JPH_PhysicsMaterial>>,
inner_body: Option<BodyId>,
collides_with_characters: bool,
mass: f32,
}
#[cfg(test)]
mod tests;