use std::ptr::NonNull;
use oxijolt_sys::*;
use super::world::sealed;
use super::SpringSettings;
use crate::body::with_read_locked_body;
use crate::limits;
use crate::{BodyId, ConstraintError, PhysicsWorld, Quat, RVec3, Real, Vec3};
pub(crate) fn check_spring(spring: SpringSettings, bound: f64) -> Result<(), ConstraintError> {
spring.validate().map_err(ConstraintError::InvalidValue)?;
if spring.fits_effective_mass(bound) {
Ok(())
} else {
Err(ConstraintError::InvalidValue(SPRING_BOUND_RULE))
}
}
const LEVER_ARM_RULE: &str = "lever-arm ratio must be at most limits::MAX_LEVER_ARM_RATIO";
struct LeverState {
can_move: bool,
inverse_mass: f32,
position: RVec3,
rotation: Quat,
center_of_mass: RVec3,
motion: Option<LeverMotion>,
}
#[derive(Clone, Copy)]
struct LeverMotion {
inverse_mass: f32,
inverse_inertia: Vec3,
principal_to_world: Quat,
}
impl LeverState {
unsafe fn read(body: NonNull<JPH_Body>) -> Self {
let body = body.as_ptr();
let mut position = RVec3::ZERO.to_jph();
let mut rotation = Quat::IDENTITY.to_jph();
let mut center_of_mass = RVec3::ZERO.to_jph();
unsafe {
JPH_Body_GetPosition(body, &mut position);
JPH_Body_GetRotation(body, &mut rotation);
JPH_Body_GetCenterOfMassPosition(body, &mut center_of_mass);
}
let rotation = Quat::from_jph(rotation);
let can_move = unsafe { JPH_Body_CanBeKinematicOrDynamic(body) };
let inverse_mass = if can_move {
unsafe {
JPH_MotionProperties_GetInverseMassUnchecked(JPH_Body_GetMotionPropertiesUnchecked(
body,
))
}
} else {
0.0
};
let motion = unsafe { JPH_Body_IsDynamic(body) }.then(|| unsafe {
let properties = JPH_Body_GetMotionProperties(body);
let mut inverse_inertia = Vec3::ZERO.to_jph();
let mut inertia_rotation = Quat::IDENTITY.to_jph();
JPH_MotionProperties_GetInverseInertiaDiagonal(properties, &mut inverse_inertia);
JPH_MotionProperties_GetInertiaRotation(properties, &mut inertia_rotation);
LeverMotion {
inverse_mass: JPH_MotionProperties_GetInverseMassUnchecked(properties),
inverse_inertia: Vec3::from_jph(inverse_inertia),
principal_to_world: rotation.product(Quat::from_jph(inertia_rotation)),
}
});
Self {
can_move,
inverse_mass,
position: RVec3::from_jph(position),
rotation,
center_of_mass: RVec3::from_jph(center_of_mass),
motion,
}
}
fn lever_from_world(&self, point: RVec3) -> Vec3 {
#[allow(clippy::unnecessary_cast)]
Vec3::new(
(point.x - self.center_of_mass.x) as f32,
(point.y - self.center_of_mass.y) as f32,
(point.z - self.center_of_mass.z) as f32,
)
}
}
fn automatic_point([first, second]: &[LeverState; 2]) -> RVec3 {
if !first.can_move {
return second.center_of_mass;
}
if !second.can_move {
return first.center_of_mass;
}
let (weight1, weight2) = (
Real::from(first.inverse_mass),
Real::from(second.inverse_mass),
);
let total = weight1 + weight2;
if total == 0.0 {
return first.center_of_mass;
}
let (c1, c2) = (first.center_of_mass, second.center_of_mass);
RVec3::new(
(weight1 * c1.x + weight2 * c2.x) / total,
(weight1 * c1.y + weight2 * c2.y) / total,
(weight1 * c1.z + weight2 * c2.z) / total,
)
}
impl LeverMotion {
fn ratio_at(self, lever: Vec3) -> f64 {
let principal = self.principal_to_world.conjugated().rotate(lever);
limits::lever_arm_ratio(self.inverse_mass, self.inverse_inertia, principal)
}
fn ratio_within(self, distance: f64) -> f64 {
limits::lever_arm_ratio_within(self.inverse_mass, self.inverse_inertia, distance)
}
}
const SPRING_BOUND_RULE: &str =
"derived spring stiffness or damping exceeds limits::MAX_SPRING_COEFFICIENT";
impl PhysicsWorld {
pub(super) fn check_lever_arms(
&self,
bodies: [BodyId; 2],
anchors: [sealed::Anchor; 2],
) -> Result<(), ConstraintError> {
let states = bodies.map(|id| {
with_read_locked_body(self.body_lock_interface, id, |body| {
unsafe { LeverState::read(body) }
})
.unwrap_or_else(|| unreachable!("checked by the caller"))
});
for (state, anchor) in states.iter().zip(anchors) {
let Some(motion) = state.motion else {
continue;
};
let (lever, radius) = match anchor {
sealed::Anchor::World(point) => (state.lever_from_world(point), 0.0),
sealed::Anchor::CenterOfMass(lever) => (state.rotation.rotate(lever), 0.0),
sealed::Anchor::OnBody1 { offset, radius } => {
let body1 = &states[0];
let turned = body1.rotation.rotate(offset);
let point = RVec3::new(
body1.position.x + Real::from(turned.x),
body1.position.y + Real::from(turned.y),
body1.position.z + Real::from(turned.z),
);
(state.lever_from_world(point), radius)
}
sealed::Anchor::BetweenCentersOfMass => {
(state.lever_from_world(automatic_point(&states)), 0.0)
}
};
let ratio = if radius == 0.0 {
motion.ratio_at(lever)
} else {
motion.ratio_within(limits::f64_length(lever) + radius)
};
if !limits::is_lever_arm_ratio(ratio) {
return Err(ConstraintError::InvalidValue(LEVER_ARM_RULE));
}
}
Ok(())
}
pub(super) fn effective_mass_bound(&self, bodies: [BodyId; 2]) -> f64 {
let bound_of = |id| {
with_read_locked_body(self.body_lock_interface, id, |body| {
unsafe {
if !JPH_Body_IsDynamic(body.as_ptr()) {
return 0.0;
}
let motion = JPH_Body_GetMotionProperties(body.as_ptr());
let mut inverse_inertia = crate::Vec3::ZERO.to_jph();
JPH_MotionProperties_GetInverseInertiaDiagonal(motion, &mut inverse_inertia);
let inverse_mass = JPH_MotionProperties_GetInverseMassUnchecked(motion);
let smallest = inverse_inertia
.x
.min(inverse_inertia.y)
.min(inverse_inertia.z);
let inertia = if smallest > 0.0 {
1.0 / f64::from(smallest)
} else {
f64::INFINITY
};
(1.0 / f64::from(inverse_mass)).max(inertia)
}
})
.unwrap_or_else(|| unreachable!("checked by the caller"))
};
bodies.into_iter().map(bound_of).fold(0.0, f64::max)
}
}