use super::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JointAxis3D {
LinearX,
LinearY,
LinearZ,
AngularX,
AngularY,
AngularZ,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum JointMotion3D {
Free,
Locked,
Limited(f32, f32),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct JointDrive3D {
pub target_position: f32,
pub target_velocity: f32,
pub stiffness: f32,
pub damping: f32,
pub max_force: f32,
}
impl JointDrive3D {
pub const fn velocity(target_velocity: f32, damping: f32) -> Self {
Self {
target_position: 0.0,
target_velocity,
stiffness: 0.0,
damping,
max_force: f32::INFINITY,
}
}
pub const fn spring(target_position: f32, stiffness: f32, damping: f32) -> Self {
Self {
target_position,
target_velocity: 0.0,
stiffness,
damping,
max_force: f32::INFINITY,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Joint3D {
pub body_a: Entity,
pub body_b: Entity,
pub frame_a: Transform3D,
pub frame_b: Transform3D,
pub axes: [JointMotion3D; 6],
pub drives: [Option<JointDrive3D>; 6],
pub contacts_enabled: bool,
pub swing_cone: Option<f32>,
}
impl Joint3D {
pub fn generic(body_a: Entity, body_b: Entity) -> Self {
Self {
body_a,
body_b,
frame_a: Transform3D::default(),
frame_b: Transform3D::default(),
axes: [JointMotion3D::Free; 6],
drives: [None; 6],
contacts_enabled: false,
swing_cone: None,
}
}
pub fn fixed(a: Entity, b: Entity) -> Self {
Self {
axes: [JointMotion3D::Locked; 6],
..Self::generic(a, b)
}
}
pub fn spherical(a: Entity, b: Entity) -> Self {
let mut j = Self::generic(a, b);
j.axes[..3].fill(JointMotion3D::Locked);
j
}
pub fn revolute(a: Entity, b: Entity) -> Self {
Self::fixed(a, b).motion(JointAxis3D::AngularX, JointMotion3D::Free)
}
pub fn prismatic(a: Entity, b: Entity) -> Self {
Self::fixed(a, b).motion(JointAxis3D::LinearX, JointMotion3D::Free)
}
#[must_use]
pub fn motion(mut self, axis: JointAxis3D, motion: JointMotion3D) -> Self {
self.axes[axis as usize] = motion;
self
}
#[must_use]
pub fn drive(mut self, axis: JointAxis3D, drive: JointDrive3D) -> Self {
self.drives[axis as usize] = Some(drive);
self
}
#[must_use]
pub fn swing_cone(mut self, radians: f32) -> Self {
self.swing_cone = Some(radians);
self
}
fn rapier(self) -> rp::GenericJoint {
let mut j = rp::GenericJoint::new(rp::JointAxesMask::empty());
j.set_local_frame1(pose_to_rp(self.frame_a.position, self.frame_a.rotation));
j.set_local_frame2(pose_to_rp(self.frame_b.position, self.frame_b.rotation));
j.set_contacts_enabled(self.contacts_enabled);
let axes = [
rp::JointAxis::LinX,
rp::JointAxis::LinY,
rp::JointAxis::LinZ,
rp::JointAxis::AngX,
rp::JointAxis::AngY,
rp::JointAxis::AngZ,
];
for (i, axis) in axes.into_iter().enumerate() {
if i >= 4 && self.swing_cone.is_some() {
continue;
}
match self.axes[i] {
JointMotion3D::Locked => {
j.locked_axes |= axis.into();
continue;
}
JointMotion3D::Limited(min, max) => {
j.set_limits(axis, [min, max]);
}
JointMotion3D::Free => {}
}
if let Some(d) = self.drives[i] {
j.set_motor(axis, d.target_position, d.target_velocity, d.stiffness, d.damping);
j.set_motor_max_force(axis, d.max_force);
}
}
if let Some(angle) = self.swing_cone {
j.coupled_axes = rp::JointAxesMask::ANG_Y | rp::JointAxesMask::ANG_Z;
j.set_limits(rp::JointAxis::AngY, [0.0, angle]);
}
j
}
}
pub(super) struct Link {
entity: Entity,
config: Joint3D,
a: rp::RigidBodyHandle,
b: rp::RigidBodyHandle,
handle: rp::ImpulseJointHandle,
seen: u64,
}
pub(super) fn sync(physics: &mut Physics3D, world: &mut World) {
world.query::<(Entity, &Joint3D)>().for_each(|(entity, config)| {
let (Some(a), Some(b)) = (physics.link(config.body_a), physics.link(config.body_b)) else {
return;
};
let (a, b) = (a.body, b.body);
if a == b {
return;
}
if let Some(old) = physics.joints.iter_mut().find(|j| j.entity == entity) {
if old.config == *config
&& old.a == a
&& old.b == b
&& physics.world.impulse_joints.get(old.handle).is_some()
{
old.seen = physics.step;
return;
}
physics.world.remove_impulse_joint(old.handle);
old.seen = 0;
}
let handle = physics.world.insert_impulse_joint(a, b, config.rapier());
physics.joints.push(Link {
entity,
config: *config,
a,
b,
handle,
seen: physics.step,
});
});
physics.joints.retain(|j| {
if j.seen == physics.step {
true
} else {
physics.world.remove_impulse_joint(j.handle);
false
}
});
}
impl Physics3D {
pub fn joint_count(&self) -> usize {
self.world.impulse_joints.len()
}
}