use concinnity_physics::{ColliderShape, DynamicParams, JointMotor, JointSpec};
use crate::components::{BodyDynamics, PhysicsJoint, PhysicsJointKind, PropCollider};
pub(crate) fn joint_spec(joint: &PhysicsJoint) -> JointSpec {
let limits = if joint.limits_enabled {
Some(joint.limits)
} else {
None
};
let motor = if joint.motor_max_force > 0.0 {
Some(JointMotor {
target_velocity: joint.motor_target_velocity,
max_force: joint.motor_max_force,
})
} else {
None
};
match joint.parsed_kind() {
PhysicsJointKind::Fixed => JointSpec::Fixed,
PhysicsJointKind::Spherical => JointSpec::Spherical,
PhysicsJointKind::Revolute => JointSpec::Revolute {
axis: joint.axis,
limits: limits.map(|[a, b]| [a.to_radians(), b.to_radians()]),
motor: motor.map(|m| JointMotor {
target_velocity: m.target_velocity.to_radians(),
max_force: m.max_force,
}),
},
PhysicsJointKind::Prismatic => JointSpec::Prismatic {
axis: joint.axis,
limits,
motor,
},
}
}
pub(crate) fn collider_shape(collider: &PropCollider, scale: [f32; 3]) -> ColliderShape {
let [sx, sy, sz] = [scale[0].abs(), scale[1].abs(), scale[2].abs()];
match collider.shape.as_str() {
"ball" | "sphere" => ColliderShape::Ball {
radius: collider.radius * sx,
},
"capsule" => ColliderShape::Capsule {
half_height: collider.half_height * sy,
radius: collider.radius * sx,
},
_ => ColliderShape::Cuboid {
half_extents: [
collider.half_extents[0] * sx,
collider.half_extents[1] * sy,
collider.half_extents[2] * sz,
],
},
}
}
pub(crate) fn dynamic_params(body: &BodyDynamics) -> DynamicParams {
DynamicParams {
mass: body.mass.max(0.0),
friction: body.friction.max(0.0),
restitution: body.restitution.clamp(0.0, 1.0),
gravity_scale: body.gravity_scale,
linear_damping: body.linear_damping.max(0.0),
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn joint_spec_converts_revolute_units_to_radians() {
let j = PhysicsJoint {
kind: "revolute".to_string(),
axis: [0.0, 0.0, 1.0],
limits_enabled: true,
limits: [-90.0, 90.0],
motor_target_velocity: 180.0,
motor_max_force: 5.0,
..Default::default()
};
match joint_spec(&j) {
JointSpec::Revolute {
axis,
limits,
motor,
} => {
assert_eq!(axis, [0.0, 0.0, 1.0]);
let lim = limits.expect("limits set");
assert!((lim[0] - (-core::f32::consts::FRAC_PI_2)).abs() < 1.0e-5);
assert!((lim[1] - core::f32::consts::FRAC_PI_2).abs() < 1.0e-5);
let m = motor.expect("motor set");
assert!((m.target_velocity - core::f32::consts::PI).abs() < 1.0e-5);
assert_eq!(m.max_force, 5.0);
}
other => panic!("expected Revolute, got {other:?}"),
}
}
#[test]
fn joint_spec_prismatic_keeps_units() {
let j = PhysicsJoint {
kind: "prismatic".to_string(),
axis: [1.0, 0.0, 0.0],
limits_enabled: true,
limits: [-0.5, 0.5],
..Default::default()
};
match joint_spec(&j) {
JointSpec::Prismatic {
axis,
limits,
motor,
} => {
assert_eq!(axis, [1.0, 0.0, 0.0]);
assert_eq!(limits, Some([-0.5, 0.5]));
assert!(motor.is_none());
}
other => panic!("expected Prismatic, got {other:?}"),
}
}
#[test]
fn joint_motor_inactive_when_max_force_zero() {
let j = PhysicsJoint {
kind: "revolute".to_string(),
motor_target_velocity: 30.0,
motor_max_force: 0.0,
..Default::default()
};
match joint_spec(&j) {
JointSpec::Revolute { motor, .. } => assert!(motor.is_none()),
_ => unreachable!(),
}
}
}