use std::f32::consts::PI;
use std::ptr::NonNull;
use oxijolt_sys::*;
use super::world::sealed::{self, ReferencedConstraint};
use super::world::ConstraintSettings;
use super::{constraint_base, ConstraintSpace, SpringSettings, FRAME_AXES_RULE};
use crate::limits;
use crate::math::is_unit;
use crate::{
BodyId, ConstraintId, ConstraintRef, GearConstraint, HingeConstraint, RackAndPinionConstraint,
SliderConstraint, Vec3,
};
const RATIO_RULE: &str =
"ratio magnitude must be between 1 / limits::MAX_RATIO and limits::MAX_RATIO";
const GEAR_RATIO_RULE: &str = "gear ratio must be between 1 and limits::MAX_GEAR_RATIO";
const REFERENCE_RULE: &str =
"a referenced hinge or slider must join the coupled body as body 2, same axis";
const SAME_DIRECTION: f32 = 1.0 - 1.0e-3;
unsafe fn constraint_axis(constraint: NonNull<JPH_Constraint>, which: u8) -> Vec3 {
let mut matrix: JPH_Mat4 = unsafe { std::mem::zeroed() };
let two_body: *const JPH_TwoBodyConstraint = constraint.as_ptr().cast();
unsafe {
if which == 1 {
JPH_TwoBodyConstraint_GetConstraintToBody1Matrix(two_body, &mut matrix);
} else {
JPH_TwoBodyConstraint_GetConstraintToBody2Matrix(two_body, &mut matrix);
}
}
let column = matrix.column[0];
Vec3::new(column.x, column.y, column.z)
}
unsafe fn check_reference(
reference: &ReferencedConstraint,
shared: BodyId,
axis: Vec3,
) -> Result<(), &'static str> {
if reference.bodies[1] != shared {
return Err(REFERENCE_RULE);
}
let reference_axis = unsafe { constraint_axis(reference.constraint, 2) };
if reference_axis.dot(axis) >= SAME_DIRECTION {
Ok(())
} else {
Err(REFERENCE_RULE)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct GearConstraintSettings {
space: ConstraintSpace,
hinge_axis1: Vec3,
hinge_axis2: Vec3,
ratio: f32,
hinges: Option<[ConstraintId<HingeConstraint>; 2]>,
}
impl Default for GearConstraintSettings {
fn default() -> Self {
let x = Vec3::new(1.0, 0.0, 0.0);
Self::new(x, x, 1.0)
}
}
impl GearConstraintSettings {
pub fn new(hinge_axis1: Vec3, hinge_axis2: Vec3, ratio: f32) -> Self {
Self {
space: ConstraintSpace::WorldSpace,
hinge_axis1,
hinge_axis2,
ratio,
hinges: None,
}
}
#[must_use]
pub fn space(mut self, value: ConstraintSpace) -> Self {
self.space = value;
self
}
#[must_use]
pub fn teeth(mut self, teeth1: u32, teeth2: u32) -> Self {
self.ratio = teeth2 as f32 / teeth1 as f32;
self
}
#[must_use]
pub fn constraints(
mut self,
hinge1: ConstraintId<HingeConstraint>,
hinge2: ConstraintId<HingeConstraint>,
) -> Self {
self.hinges = Some([hinge1, hinge2]);
self
}
}
impl sealed::Settings for GearConstraintSettings {
const NEEDS_DYNAMIC_BODIES: bool = true;
fn validate(&self) -> Result<(), &'static str> {
if !(is_unit(self.hinge_axis1) && is_unit(self.hinge_axis2)) {
return Err(FRAME_AXES_RULE);
}
if !(1.0..=limits::MAX_GEAR_RATIO).contains(&self.ratio) {
return Err(GEAR_RATIO_RULE);
}
Ok(())
}
fn springs(&self) -> Vec<SpringSettings> {
Vec::new()
}
fn references(&self) -> [Option<crate::AnyConstraintId>; 2] {
match self.hinges {
Some([hinge1, hinge2]) => [Some(hinge1.into()), Some(hinge2.into())],
None => [None, None],
}
}
unsafe fn create(
&self,
body1: NonNull<JPH_Body>,
body2: NonNull<JPH_Body>,
) -> *mut JPH_Constraint {
let settings = JPH_GearConstraintSettings {
base: constraint_base(),
space: self.space.to_jph(),
hingeAxis1: self.hinge_axis1.to_jph(),
hingeAxis2: self.hinge_axis2.to_jph(),
ratio: self.ratio,
};
unsafe { JPH_GearConstraint_Create(&settings, body1.as_ptr(), body2.as_ptr()) }.cast()
}
unsafe fn attach_references(
&self,
constraint: NonNull<JPH_Constraint>,
bodies: [BodyId; 2],
references: [Option<ReferencedConstraint>; 2],
) -> Result<(), &'static str> {
let [Some(hinge1), Some(hinge2)] = references else {
return Ok(());
};
unsafe {
check_reference(&hinge1, bodies[0], constraint_axis(constraint, 1))?;
check_reference(&hinge2, bodies[1], constraint_axis(constraint, 2))?;
JPH_GearConstraint_SetConstraints(
constraint.as_ptr().cast(),
hinge1.constraint.as_ptr(),
hinge2.constraint.as_ptr(),
);
}
Ok(())
}
}
impl ConstraintSettings for GearConstraintSettings {
type Kind = GearConstraint;
}
impl ConstraintRef<'_, GearConstraint> {
pub fn total_lambda(&self) -> f32 {
unsafe { JPH_GearConstraint_GetTotalLambda(self.ptr()) }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RackAndPinionConstraintSettings {
space: ConstraintSpace,
hinge_axis: Vec3,
slider_axis: Vec3,
ratio: f32,
constraints: Option<(
ConstraintId<HingeConstraint>,
ConstraintId<SliderConstraint>,
)>,
}
impl Default for RackAndPinionConstraintSettings {
fn default() -> Self {
let x = Vec3::new(1.0, 0.0, 0.0);
Self::new(x, x, 1.0)
}
}
impl RackAndPinionConstraintSettings {
pub fn new(hinge_axis: Vec3, slider_axis: Vec3, ratio: f32) -> Self {
Self {
space: ConstraintSpace::WorldSpace,
hinge_axis,
slider_axis,
ratio,
constraints: None,
}
}
#[must_use]
pub fn space(mut self, value: ConstraintSpace) -> Self {
self.space = value;
self
}
#[must_use]
pub fn teeth(mut self, rack_teeth: u32, rack_length: f32, pinion_teeth: u32) -> Self {
let length_valid = rack_length > 0.0 && rack_length <= limits::MAX_SHAPE_EXTENT;
self.ratio = if length_valid && rack_teeth > 0 && pinion_teeth > 0 {
2.0 * PI * rack_teeth as f32 / (rack_length * pinion_teeth as f32)
} else {
f32::NAN
};
self
}
#[must_use]
pub fn constraints(
mut self,
hinge: ConstraintId<HingeConstraint>,
slider: ConstraintId<SliderConstraint>,
) -> Self {
self.constraints = Some((hinge, slider));
self
}
fn to_jph(&self) -> JPH_RackAndPinionConstraintSettings {
JPH_RackAndPinionConstraintSettings {
base: constraint_base(),
space: self.space.to_jph(),
hingeAxis: self.hinge_axis.to_jph(),
sliderAxis: self.slider_axis.to_jph(),
ratio: self.ratio,
}
}
}
impl sealed::Settings for RackAndPinionConstraintSettings {
const NEEDS_DYNAMIC_BODIES: bool = true;
fn validate(&self) -> Result<(), &'static str> {
if !(is_unit(self.hinge_axis) && is_unit(self.slider_axis)) {
return Err(FRAME_AXES_RULE);
}
if !limits::is_ratio(self.ratio) {
return Err(RATIO_RULE);
}
Ok(())
}
fn springs(&self) -> Vec<SpringSettings> {
Vec::new()
}
fn references(&self) -> [Option<crate::AnyConstraintId>; 2] {
match self.constraints {
Some((hinge, slider)) => [Some(hinge.into()), Some(slider.into())],
None => [None, None],
}
}
unsafe fn create(
&self,
body1: NonNull<JPH_Body>,
body2: NonNull<JPH_Body>,
) -> *mut JPH_Constraint {
let settings = self.to_jph();
unsafe { JPH_RackAndPinionConstraint_Create(&settings, body1.as_ptr(), body2.as_ptr()) }
.cast()
}
unsafe fn attach_references(
&self,
constraint: NonNull<JPH_Constraint>,
bodies: [BodyId; 2],
references: [Option<ReferencedConstraint>; 2],
) -> Result<(), &'static str> {
let [Some(hinge), Some(slider)] = references else {
return Ok(());
};
unsafe {
check_reference(&hinge, bodies[0], constraint_axis(constraint, 1))?;
check_reference(&slider, bodies[1], constraint_axis(constraint, 2))?;
JPH_RackAndPinionConstraint_SetConstraints(
constraint.as_ptr().cast(),
hinge.constraint.as_ptr(),
slider.constraint.as_ptr(),
);
}
Ok(())
}
}
impl ConstraintSettings for RackAndPinionConstraintSettings {
type Kind = RackAndPinionConstraint;
}
impl ConstraintRef<'_, RackAndPinionConstraint> {
pub fn total_lambda(&self) -> f32 {
unsafe { JPH_RackAndPinionConstraint_GetTotalLambda(self.ptr()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::world::ensure_initialized;
#[test]
fn rack_and_pinion_defaults_are_jolts() {
assert!(ensure_initialized());
let mut jolt: JPH_RackAndPinionConstraintSettings = unsafe { std::mem::zeroed() };
unsafe { JPH_RackAndPinionConstraintSettings_Init(&mut jolt) };
let ours = RackAndPinionConstraintSettings::default().to_jph();
assert_eq!(ours.base.enabled, jolt.base.enabled);
assert_eq!(ours.space, jolt.space);
assert_eq!(
Vec3::from_jph(ours.hingeAxis),
Vec3::from_jph(jolt.hingeAxis)
);
assert_eq!(
Vec3::from_jph(ours.sliderAxis),
Vec3::from_jph(jolt.sliderAxis)
);
assert_eq!(ours.ratio.to_bits(), jolt.ratio.to_bits());
}
}