use std::collections::BTreeSet;
use std::hash::Hash;
use std::ptr::NonNull;
use oxijolt_sys::*;
use super::id::{AnyConstraintId, ConstraintId};
use super::lever::check_spring;
use super::SpringSettings;
use crate::body::with_locked_bodies;
use crate::owned::{JoltObject, Owned};
use crate::{
AllowedDofs, BodyError, BodyId, ConstraintError, MotionType, PhysicsWorld, RVec3, Vec3,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ConstraintType {
Fixed,
Point,
Distance,
Hinge,
Slider,
Cone,
SwingTwist,
SixDof,
Gear,
RackAndPinion,
Pulley,
Path,
}
pub(crate) mod sealed {
use super::*;
pub trait Kind {
const TYPE: ConstraintType;
const SUB_TYPE: JPH_ConstraintSubType;
}
#[derive(Clone, Copy, Debug)]
pub enum Anchor {
World(RVec3),
CenterOfMass(Vec3),
OnBody1 {
offset: Vec3,
radius: f64,
},
BetweenCentersOfMass,
}
pub trait Settings {
const NEEDS_DYNAMIC_BODIES: bool = false;
fn validate(&self) -> Result<(), &'static str>;
fn anchors(&self) -> Option<[Anchor; 2]> {
None
}
fn springs(&self) -> Vec<SpringSettings>;
fn references(&self) -> [Option<AnyConstraintId>; 2] {
[None, None]
}
unsafe fn create(
&self,
body1: NonNull<JPH_Body>,
body2: NonNull<JPH_Body>,
) -> *mut JPH_Constraint;
unsafe fn attach_references(
&self,
_constraint: NonNull<JPH_Constraint>,
_bodies: [BodyId; 2],
_references: [Option<ReferencedConstraint>; 2],
) -> Result<(), &'static str> {
Ok(())
}
}
pub struct ReferencedConstraint {
pub(crate) constraint: NonNull<JPH_Constraint>,
pub(crate) bodies: [BodyId; 2],
}
impl std::fmt::Debug for ReferencedConstraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReferencedConstraint")
.field("bodies", &self.bodies)
.finish_non_exhaustive()
}
}
}
pub trait ConstraintKind: sealed::Kind {}
pub trait ConstraintSettings: sealed::Settings {
type Kind: ConstraintKind;
}
macro_rules! constraint_kinds {
($($(#[$doc:meta])* $name:ident => $kind:ident, $sub_type:ident;)*) => {
$(
$(#[$doc])*
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum $name {}
impl sealed::Kind for $name {
const TYPE: ConstraintType = ConstraintType::$kind;
const SUB_TYPE: JPH_ConstraintSubType = $sub_type;
}
impl ConstraintKind for $name {}
)*
};
}
constraint_kinds! {
FixedConstraint => Fixed, JPH_ConstraintSubType_Fixed;
PointConstraint => Point, JPH_ConstraintSubType_Point;
DistanceConstraint => Distance, JPH_ConstraintSubType_Distance;
HingeConstraint => Hinge, JPH_ConstraintSubType_Hinge;
SliderConstraint => Slider, JPH_ConstraintSubType_Slider;
ConeConstraint => Cone, JPH_ConstraintSubType_Cone;
SwingTwistConstraint => SwingTwist, JPH_ConstraintSubType_SwingTwist;
SixDofConstraint => SixDof, JPH_ConstraintSubType_SixDOF;
GearConstraint => Gear, JPH_ConstraintSubType_Gear;
RackAndPinionConstraint => RackAndPinion, JPH_ConstraintSubType_RackAndPinion;
PulleyConstraint => Pulley, JPH_ConstraintSubType_Pulley;
PathConstraint => Path, JPH_ConstraintSubType_Path;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum MotorState {
#[default]
Off,
Velocity,
Position,
}
impl MotorState {
pub(crate) fn to_jph(self) -> JPH_MotorState {
match self {
Self::Off => JPH_MotorState_Off,
Self::Velocity => JPH_MotorState_Velocity,
Self::Position => JPH_MotorState_Position,
}
}
pub(crate) fn from_jph(state: JPH_MotorState) -> Self {
if state == JPH_MotorState_Velocity {
Self::Velocity
} else if state == JPH_MotorState_Position {
Self::Position
} else {
Self::Off
}
}
}
impl JoltObject for JPH_Constraint {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_Constraint_Destroy(ptr) };
}
}
pub(crate) struct ConstraintEntry {
pub(crate) constraint: Owned<JPH_Constraint>,
pub(crate) kind: ConstraintType,
pub(crate) bodies: [BodyId; 2],
pub(crate) effective_mass_bound: f64,
pub(crate) references: [Option<u32>; 2],
pub(crate) referenced_by: BTreeSet<u32>,
}
pub struct ConstraintRef<'w, K> {
id: ConstraintId<K>,
entry: &'w ConstraintEntry,
}
impl<K: ConstraintKind> std::fmt::Debug for ConstraintRef<'_, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConstraintRef")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl<K: ConstraintKind> ConstraintRef<'_, K> {
pub(crate) fn ptr<T>(&self) -> *mut T {
self.entry.constraint.as_ptr().cast()
}
pub fn id(&self) -> ConstraintId<K> {
self.id
}
pub fn bodies(&self) -> [BodyId; 2] {
self.entry.bodies
}
pub fn is_enabled(&self) -> bool {
unsafe { JPH_Constraint_GetEnabled(self.ptr()) }
}
pub fn is_active(&self) -> bool {
unsafe { JPH_Constraint_IsActive(self.ptr()) }
}
}
pub struct ConstraintMut<'w, K> {
world: &'w mut PhysicsWorld,
id: ConstraintId<K>,
}
impl<K: ConstraintKind> std::fmt::Debug for ConstraintMut<'_, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConstraintMut")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl<K: ConstraintKind> ConstraintMut<'_, K> {
pub(crate) fn entry(&self) -> &ConstraintEntry {
self.world
.constraints
.get(&self.id.raw)
.unwrap_or_else(|| unreachable!("checked when the view was made"))
}
pub(crate) fn ptr<T>(&self) -> *mut T {
self.entry().constraint.as_ptr().cast()
}
pub fn id(&self) -> ConstraintId<K> {
self.id
}
pub fn bodies(&self) -> [BodyId; 2] {
self.entry().bodies
}
pub fn is_enabled(&self) -> bool {
unsafe { JPH_Constraint_GetEnabled(self.ptr()) }
}
pub fn is_active(&self) -> bool {
unsafe { JPH_Constraint_IsActive(self.ptr()) }
}
pub fn set_enabled(&mut self, enabled: bool) {
unsafe { JPH_Constraint_SetEnabled(self.ptr(), enabled) };
self.wake_bodies();
}
pub(crate) fn wake_bodies(&mut self) {
let bodies = self.entry().bodies;
self.world.wake_constraint_bodies(bodies);
}
pub(crate) fn effective_mass_bound(&self) -> f64 {
self.entry().effective_mass_bound
}
}
impl PhysicsWorld {
pub fn create_constraint<S: ConstraintSettings>(
&mut self,
body1: BodyId,
body2: BodyId,
settings: &S,
) -> Result<ConstraintId<S::Kind>, ConstraintError> {
sealed::Settings::validate(settings).map_err(ConstraintError::InvalidValue)?;
for body in [body1, body2] {
self.check(body).map_err(ConstraintError::Body)?;
}
if body1 == body2 {
return Err(ConstraintError::InvalidValue(
"a constraint joins two different bodies",
));
}
for body in [body1, body2] {
if self.is_inner_body(body) {
return Err(ConstraintError::Body(BodyError::OwnedByCharacter(body)));
}
if self.is_ragdoll_body(body) {
return Err(ConstraintError::Body(BodyError::OwnedByRagdoll(body)));
}
if self
.body(body)
.map_err(ConstraintError::Body)?
.is_soft_body()
{
return Err(ConstraintError::Body(BodyError::NotRigidBody(body)));
}
let allowed_dofs = self
.body(body)
.map_err(ConstraintError::Body)?
.allowed_dofs();
if allowed_dofs != AllowedDofs::ALL {
return Err(ConstraintError::Body(BodyError::RestrictedDofs(body)));
}
if S::NEEDS_DYNAMIC_BODIES
&& self
.body(body)
.map_err(ConstraintError::Body)?
.motion_type()
!= MotionType::Dynamic
{
return Err(ConstraintError::NotDynamic(body));
}
}
if let Some(anchors) = settings.anchors() {
self.check_lever_arms([body1, body2], anchors)?;
}
let references = settings.references();
for reference in references.into_iter().flatten() {
self.constraint_entry(reference)?;
}
let raw = self.next_constraint_id;
if raw == u32::MAX {
return Err(ConstraintError::TooManyConstraints);
}
let effective_mass_bound = self.effective_mass_bound([body1, body2]);
for spring in settings.springs() {
check_spring(spring, effective_mass_bound)?;
}
let constraint =
with_locked_bodies(self.body_lock_interface, [body1, body2], |first, second| {
unsafe { Owned::from_raw(sealed::Settings::create(settings, first, second)) }
})
.unwrap_or_else(|| unreachable!("`check` found both bodies and `&mut self` keeps them"))
.unwrap_or_else(|| unreachable!("joltc `new`s the constraint"));
debug_assert_eq!(
unsafe { JPH_Constraint_GetSubType(constraint.as_ptr()) },
<S::Kind as sealed::Kind>::SUB_TYPE
);
let referenced = references.map(|reference| {
reference.map(|id| {
let entry = &self.constraints[&id.raw];
sealed::ReferencedConstraint {
constraint: entry.constraint.as_non_null(),
bodies: entry.bodies,
}
})
});
let attached = unsafe {
sealed::Settings::attach_references(
settings,
constraint.as_non_null(),
[body1, body2],
referenced,
)
};
attached.map_err(ConstraintError::InvalidValue)?;
self.note_structure_change();
unsafe { JPH_PhysicsSystem_AddConstraint(self.system.as_ptr(), constraint.as_ptr()) };
let references = references.map(|reference| reference.map(|id| id.raw));
for reference in references.into_iter().flatten() {
if let Some(entry) = self.constraints.get_mut(&reference) {
entry.referenced_by.insert(raw);
}
}
for body in [body1, body2] {
self.constraint_bodies
.entry(body.to_raw())
.or_default()
.insert(raw);
}
self.constraints.insert(
raw,
ConstraintEntry {
constraint,
kind: <S::Kind as sealed::Kind>::TYPE,
bodies: [body1, body2],
effective_mass_bound,
references,
referenced_by: BTreeSet::new(),
},
);
self.next_constraint_id += 1;
self.wake_constraint_bodies([body1, body2]);
Ok(ConstraintId::new(raw, self.tag))
}
pub(crate) fn constraint_entry(
&self,
id: impl Into<AnyConstraintId>,
) -> Result<&ConstraintEntry, ConstraintError> {
let id = id.into();
if id.world != self.tag {
return Err(ConstraintError::WrongWorld(id));
}
self.constraints
.get(&id.raw)
.ok_or(ConstraintError::NotFound(id))
}
pub fn remove_constraint(
&mut self,
id: impl Into<AnyConstraintId>,
) -> Result<(), ConstraintError> {
let id = id.into();
let entry = self.constraint_entry(id)?;
if let Some(&dependent) = entry.referenced_by.first() {
let dependent = AnyConstraintId {
raw: dependent,
world: self.tag,
kind: self.constraints[&dependent].kind,
};
return Err(ConstraintError::UsedByConstraint(dependent));
}
self.note_structure_change();
let entry = self
.constraints
.remove(&id.raw)
.unwrap_or_else(|| unreachable!("checked above"));
let bodies = entry.bodies;
self.unregister_constraint(id.raw, entry);
self.wake_constraint_bodies(bodies);
Ok(())
}
fn unregister_constraint(&mut self, raw: u32, entry: ConstraintEntry) {
unsafe {
JPH_PhysicsSystem_RemoveConstraint(self.system.as_ptr(), entry.constraint.as_ptr())
};
for body in entry.bodies {
if let Some(ids) = self.constraint_bodies.get_mut(&body.to_raw()) {
ids.remove(&raw);
if ids.is_empty() {
self.constraint_bodies.remove(&body.to_raw());
}
}
}
for reference in entry.references.into_iter().flatten() {
if let Some(referenced) = self.constraints.get_mut(&reference) {
referenced.referenced_by.remove(&raw);
}
}
drop(entry);
}
pub(crate) fn remove_all_constraints(&mut self) {
while let Some((raw, entry)) = self.constraints.pop_last() {
self.unregister_constraint(raw, entry);
}
}
pub(crate) fn wake_constraint_bodies(&mut self, bodies: [BodyId; 2]) {
let mut ids = bodies.map(BodyId::to_raw);
ids.sort_unstable();
unsafe { JPH_BodyInterface_ActivateBodies(self.body_interface.as_ptr(), ids.as_ptr(), 2) };
}
pub fn constraint<K: ConstraintKind>(
&self,
id: ConstraintId<K>,
) -> Result<ConstraintRef<'_, K>, ConstraintError> {
let entry = self.constraint_entry(id)?;
debug_assert_eq!(entry.kind, K::TYPE, "ids are typed by their kind");
Ok(ConstraintRef { id, entry })
}
pub fn constraint_mut<K: ConstraintKind>(
&mut self,
id: ConstraintId<K>,
) -> Result<ConstraintMut<'_, K>, ConstraintError> {
let entry = self.constraint_entry(id)?;
debug_assert_eq!(entry.kind, K::TYPE, "ids are typed by their kind");
Ok(ConstraintMut { world: self, id })
}
pub fn constraint_ids(&self) -> impl Iterator<Item = AnyConstraintId> + '_ {
self.constraints
.iter()
.map(|(&raw, entry)| AnyConstraintId {
raw,
world: self.tag,
kind: entry.kind,
})
}
pub fn constraints_of_body(&self, body: BodyId) -> impl Iterator<Item = AnyConstraintId> + '_ {
let raws = if body.world == self.tag {
self.constraint_bodies.get(&body.to_raw())
} else {
None
};
raws.into_iter().flatten().map(|&raw| AnyConstraintId {
raw,
world: self.tag,
kind: self.constraints[&raw].kind,
})
}
pub fn constraint_count(&self) -> u32 {
self.constraints.len() as u32
}
pub fn contains_constraint(&self, id: impl Into<AnyConstraintId>) -> bool {
self.constraint_entry(id).is_ok()
}
pub(crate) fn is_constraint_body(&self, body: BodyId) -> bool {
body.world == self.tag && self.constraint_bodies.contains_key(&body.to_raw())
}
}