use std::ptr::NonNull;
use oxijolt_sys::*;
use super::world::{sealed, ConstraintSettings};
use super::{
constraint_base, point_anchors, validate_point, within, ConstraintSpace, SpringSettings,
};
use crate::body::with_locked_bodies;
use crate::limits;
use crate::owned::Owned;
use crate::{
ConstraintError, ConstraintMut, ConstraintRef, ConstraintType, PhysicsWorld, PulleyConstraint,
RVec3,
};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum PulleyLength {
#[default]
UpToCurrent,
Range {
min: f32,
max: f32,
},
}
fn validate_pulley_length(min: f32, max: f32, ratio: f32) -> Result<(), &'static str> {
let longest = (1.0 + ratio) * limits::MAX_SHAPE_EXTENT;
if within(min, 0.0, longest) && within(max, 0.0, longest) && min <= max {
Ok(())
} else {
Err("pulley lengths must be 0 <= min <= max <= (1 + ratio) * limits::MAX_SHAPE_EXTENT")
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PulleyConstraintSettings {
space: ConstraintSpace,
body_point1: RVec3,
fixed_point1: RVec3,
body_point2: RVec3,
fixed_point2: RVec3,
ratio: f32,
length: PulleyLength,
}
impl Default for PulleyConstraintSettings {
fn default() -> Self {
Self::new(RVec3::ZERO, RVec3::ZERO, RVec3::ZERO, RVec3::ZERO)
}
}
impl PulleyConstraintSettings {
pub fn new(
body_point1: RVec3,
fixed_point1: RVec3,
body_point2: RVec3,
fixed_point2: RVec3,
) -> Self {
Self {
space: ConstraintSpace::WorldSpace,
body_point1,
fixed_point1,
body_point2,
fixed_point2,
ratio: 1.0,
length: PulleyLength::UpToCurrent,
}
}
#[must_use]
pub fn space(mut self, value: ConstraintSpace) -> Self {
self.space = value;
self
}
#[must_use]
pub fn ratio(mut self, value: f32) -> Self {
self.ratio = value;
self
}
#[must_use]
pub fn length(mut self, value: PulleyLength) -> Self {
self.length = value;
self
}
fn to_jph(&self) -> JPH_PulleyConstraintSettings {
let (min, max) = match self.length {
PulleyLength::UpToCurrent => (0.0, -1.0),
PulleyLength::Range { min, max } => (min, max),
};
JPH_PulleyConstraintSettings {
base: constraint_base(),
space: self.space.to_jph(),
bodyPoint1: self.body_point1.to_jph(),
fixedPoint1: self.fixed_point1.to_jph(),
bodyPoint2: self.body_point2.to_jph(),
fixedPoint2: self.fixed_point2.to_jph(),
ratio: self.ratio,
minLength: min,
maxLength: max,
}
}
}
impl sealed::Settings for PulleyConstraintSettings {
const NEEDS_DYNAMIC_BODIES: bool = true;
fn validate(&self) -> Result<(), &'static str> {
for point in [
self.body_point1,
self.fixed_point1,
self.body_point2,
self.fixed_point2,
] {
validate_point(point)?;
}
if !(self.ratio > 0.0 && limits::is_ratio(self.ratio)) {
return Err(
"pulley ratio must be positive and within 1 / limits::MAX_RATIO..=limits::MAX_RATIO",
);
}
if let PulleyLength::Range { min, max } = self.length {
validate_pulley_length(min, max, self.ratio)?;
}
Ok(())
}
fn springs(&self) -> Vec<SpringSettings> {
Vec::new()
}
fn anchors(&self) -> Option<[sealed::Anchor; 2]> {
Some(point_anchors(
self.space,
self.body_point1,
self.body_point2,
))
}
unsafe fn create(
&self,
body1: NonNull<JPH_Body>,
body2: NonNull<JPH_Body>,
) -> *mut JPH_Constraint {
let settings = self.to_jph();
unsafe { JPH_PulleyConstraint_Create(&settings, body1.as_ptr(), body2.as_ptr()) }.cast()
}
}
impl ConstraintSettings for PulleyConstraintSettings {
type Kind = PulleyConstraint;
}
unsafe fn pulley_settings(pulley: *const JPH_PulleyConstraint) -> JPH_PulleyConstraintSettings {
let mut settings: JPH_PulleyConstraintSettings = unsafe { std::mem::zeroed() };
unsafe { JPH_PulleyConstraint_GetSettings(pulley, &mut settings) };
settings
}
impl ConstraintRef<'_, PulleyConstraint> {
pub fn current_length(&self) -> f32 {
unsafe { JPH_PulleyConstraint_GetCurrentLength(self.ptr()) }
}
pub fn min_length(&self) -> f32 {
unsafe { JPH_PulleyConstraint_GetMinLength(self.ptr()) }
}
pub fn max_length(&self) -> f32 {
unsafe { JPH_PulleyConstraint_GetMaxLength(self.ptr()) }
}
pub fn ratio(&self) -> f32 {
unsafe { pulley_settings(self.ptr()) }.ratio
}
pub fn total_lambda_position(&self) -> f32 {
unsafe { JPH_PulleyConstraint_GetTotalLambdaPosition(self.ptr()) }
}
}
impl ConstraintMut<'_, PulleyConstraint> {
pub fn set_length(&mut self, min: f32, max: f32) -> Result<(), ConstraintError> {
let ratio = unsafe { pulley_settings(self.ptr()) }.ratio;
validate_pulley_length(min, max, ratio).map_err(ConstraintError::InvalidValue)?;
unsafe { JPH_PulleyConstraint_SetLength(self.ptr(), min, max) };
self.wake_bodies();
Ok(())
}
}
pub(crate) struct PulleyRebase {
raw: u32,
settings: JPH_PulleyConstraintSettings,
}
impl PhysicsWorld {
pub(crate) fn rebased_pulleys(
&self,
point: impl Fn(RVec3) -> RVec3,
) -> Result<Vec<PulleyRebase>, &'static str> {
let mut rebased = Vec::new();
for (&raw, entry) in &self.constraints {
if entry.kind != ConstraintType::Pulley {
continue;
}
let mut settings = unsafe { pulley_settings(entry.constraint.as_ptr().cast()) };
let fixed1 = point(RVec3::from_jph(settings.fixedPoint1));
let fixed2 = point(RVec3::from_jph(settings.fixedPoint2));
if !(fixed1.is_finite() && fixed2.is_finite()) {
return Err("rebase would give a pulley a non-finite fixed point");
}
settings.fixedPoint1 = fixed1.to_jph();
settings.fixedPoint2 = fixed2.to_jph();
rebased.push(PulleyRebase { raw, settings });
}
Ok(rebased)
}
pub(crate) fn apply_pulley_rebase(&mut self, rebased: Vec<PulleyRebase>) {
for pulley in rebased {
let entry = self
.constraints
.get(&pulley.raw)
.unwrap_or_else(|| unreachable!("computed from this world's pulleys"));
let replacement = with_locked_bodies(self.body_lock_interface, entry.bodies, |a, b| {
unsafe {
Owned::from_raw(
JPH_PulleyConstraint_Create(&pulley.settings, a.as_ptr(), b.as_ptr())
.cast::<JPH_Constraint>(),
)
}
})
.unwrap_or_else(|| unreachable!("a constraint's bodies stay in the world"))
.unwrap_or_else(|| unreachable!("joltc `new`s the constraint"));
unsafe {
JPH_PhysicsSystem_RemoveConstraint(self.system.as_ptr(), entry.constraint.as_ptr());
JPH_PhysicsSystem_AddConstraint(self.system.as_ptr(), replacement.as_ptr());
}
let entry = self
.constraints
.get_mut(&pulley.raw)
.unwrap_or_else(|| unreachable!("looked up above"));
entry.constraint = replacement;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::world::ensure_initialized;
#[test]
fn pulley_defaults_are_jolts() {
assert!(ensure_initialized());
let mut jolt: JPH_PulleyConstraintSettings = unsafe { std::mem::zeroed() };
unsafe { JPH_PulleyConstraintSettings_Init(&mut jolt) };
let ours = PulleyConstraintSettings::default().to_jph();
assert_eq!(ours.base.enabled, jolt.base.enabled);
assert_eq!(ours.space, jolt.space);
for (a, b) in [
(ours.bodyPoint1, jolt.bodyPoint1),
(ours.fixedPoint1, jolt.fixedPoint1),
(ours.bodyPoint2, jolt.bodyPoint2),
(ours.fixedPoint2, jolt.fixedPoint2),
] {
assert_eq!(RVec3::from_jph(a), RVec3::from_jph(b));
}
assert_eq!(ours.ratio.to_bits(), jolt.ratio.to_bits());
assert_eq!(ours.minLength.to_bits(), jolt.minLength.to_bits());
assert_eq!(ours.maxLength.to_bits(), jolt.maxLength.to_bits());
}
}