use mjcf_rs::body as mb;
use mjcf_rs::compiler::InertiaFromGeom;
use mjcf_rs::model::BodyEntry;
use rapier3d::dynamics::{
MassProperties, Multibody, MultibodyDofCoupling, MultibodyJointHandle, MultibodyJointSet,
RigidBodyHandle, RigidBodySet,
};
use rapier3d::math::{Matrix, Pose, Real, Vector};
use super::conversion::Conversion;
impl<'a> Conversion<'a> {
pub(super) fn derive_mass_properties(&self, entry: &BodyEntry) -> Option<MassProperties> {
let s = self.options.scale;
let from_inertial = entry
.body
.inertial
.as_ref()
.map(|i| inertial_to_mass_props(i, s));
let force_geom = self.model.compiler.inertia_from_geom == InertiaFromGeom::True;
let auto_geom = self.model.compiler.inertia_from_geom == InertiaFromGeom::Auto
&& entry.body.inertial.is_none();
let mp = if force_geom || auto_geom {
self.geoms_to_mass_props(entry).or(from_inertial)
} else {
from_inertial
};
mp.map(|mp_| clamp_mass_properties(mp_, &self.model.compiler))
}
pub(super) fn geoms_to_mass_props(&self, entry: &BodyEntry) -> Option<MassProperties> {
let density_default = self.model.compiler.inertia_density;
let mut acc: Option<MassProperties> = None;
for g in &entry.body.geoms {
let (shape, body_frame_pose) = match self.build_geom_shape(g) {
Some(s) => s,
None => continue,
};
let density = if let Some(m) = g.mass {
let v = shape.mass_properties(1.0).mass();
if v > 1e-30 { (m as Real) / v } else { 0.0 }
} else {
g.density.unwrap_or(density_default) as Real
};
if density <= 0.0 {
continue;
}
let mp_geom = shape.mass_properties(density);
let mp_local = mp_geom.transform_by(&body_frame_pose);
acc = Some(match acc {
None => mp_local,
Some(a) => a + mp_local,
});
}
acc
}
}
pub(super) fn inertial_to_mass_props(i: &mb::Inertial, scale: Real) -> MassProperties {
let com = Vector::new(
i.pose.translation.x as Real * scale,
i.pose.translation.y as Real * scale,
i.pose.translation.z as Real * scale,
);
let s2 = scale * scale;
match i.inertia {
mb::InertiaSpec::Diagonal(d) => {
let principal = Vector::new(d[0] as Real * s2, d[1] as Real * s2, d[2] as Real * s2);
let r = Pose::from(i.pose).rotation;
MassProperties::with_principal_inertia_frame(com, i.mass as Real, principal, r)
}
mb::InertiaSpec::Full(f) => {
let ixx = f[0] as Real * s2;
let iyy = f[1] as Real * s2;
let izz = f[2] as Real * s2;
let ixy = f[3] as Real * s2;
let ixz = f[4] as Real * s2;
let iyz = f[5] as Real * s2;
let m = Matrix::from_cols_array(&[ixx, ixy, ixz, ixy, iyy, iyz, ixz, iyz, izz]);
MassProperties::with_inertia_matrix(com, i.mass as Real, m)
}
}
}
pub(super) fn scale_mass_properties(mp: MassProperties, factor: Real) -> MassProperties {
MassProperties::with_principal_inertia_frame(
mp.local_com,
mp.mass() * factor,
mp.principal_inertia() * factor,
mp.principal_inertia_local_frame,
)
}
pub(super) fn clamp_mass_properties(
mp: MassProperties,
compiler: &mjcf_rs::compiler::Compiler,
) -> MassProperties {
let mass = mp.mass();
let bm = compiler.bound_mass as Real;
let new_mass = if bm > 0.0 && mass < bm { bm } else { mass };
let principal = mp.principal_inertia();
let bi = compiler.bound_inertia as Real;
let mut clamped = principal;
if bi > 0.0 {
for i in 0..3 {
if clamped[i] < bi {
clamped[i] = bi;
}
}
}
if compiler.balance_inertia {
let a = (clamped[1] - clamped[2]).abs();
if clamped[0] < a {
clamped[0] = a;
}
let b = (clamped[0] - clamped[2]).abs();
if clamped[1] < b {
clamped[1] = b;
}
let c = (clamped[0] - clamped[1]).abs();
if clamped[2] < c {
clamped[2] = c;
}
}
MassProperties::with_principal_inertia_frame(
mp.local_com,
new_mass,
clamped,
mp.principal_inertia_local_frame,
)
}
pub(super) fn move_motor_damping_to_multibody(
multibody_joints: &mut MultibodyJointSet,
handle: MultibodyJointHandle,
damping: Real,
) {
use rapier3d::math::SPATIAL_DIM;
let Some((multibody, link_id)) = multibody_joints.get_mut(handle) else {
return;
};
let mut offset = 0;
for (i, link) in multibody.links().enumerate() {
if i == link_id {
break;
}
offset += link.joint().ndofs();
}
let Some(link) = multibody.links_mut().nth(link_id) else {
return;
};
let locked_bits = link.joint.data.locked_axes.bits();
let motor_bits = link.joint.data.motor_axes.bits();
for i in 0..SPATIAL_DIM {
if (motor_bits & (1 << i)) != 0 {
link.joint.data.motors[i].damping = 0.0;
}
}
let _ = link;
let damping_vec = multibody.damping_mut();
let mut local_dof = 0;
for i in 0..SPATIAL_DIM {
if (locked_bits & (1 << i)) == 0 {
let idx = offset + local_dof;
if idx < damping_vec.len() {
damping_vec[idx] = damping;
}
local_dof += 1;
}
}
}
pub(super) fn add_armature_to_multibody(
multibody_joints: &mut MultibodyJointSet,
handle: MultibodyJointHandle,
armature: Real,
) {
use rapier3d::math::SPATIAL_DIM;
let Some((multibody, link_id)) = multibody_joints.get_mut(handle) else {
return;
};
let mut offset = 0;
for (i, link) in multibody.links().enumerate() {
if i == link_id {
break;
}
offset += link.joint().ndofs();
}
let Some(link) = multibody.links().nth(link_id) else {
return;
};
let locked_bits = link.joint.data.locked_axes.bits();
let armature_vec = multibody.armature_mut();
let mut local_dof = 0;
for i in 0..SPATIAL_DIM {
if (locked_bits & (1 << i)) == 0 {
let idx = offset + local_dof;
if idx < armature_vec.len() {
armature_vec[idx] = armature;
}
local_dof += 1;
}
}
}
pub(super) fn add_spring_to_multibody(
multibody_joints: &mut MultibodyJointSet,
handle: MultibodyJointHandle,
stiffness: Real,
rest: Real,
) {
use rapier3d::dynamics::JointAxesMask;
use rapier3d::math::SPATIAL_DIM;
let Some((multibody, link_id)) = multibody_joints.get_mut(handle) else {
return;
};
let Some(link) = multibody.links_mut().nth(link_id) else {
return;
};
let locked_bits = link.joint.data.locked_axes.bits();
for axis in 0..SPATIAL_DIM {
if (locked_bits & (1 << axis)) == 0 {
link.joint.set_spring(axis, stiffness, rest);
if let Some(flag) = JointAxesMask::from_bits(1u8 << axis) {
link.joint.data.motor_axes.remove(flag);
}
}
}
}
pub(super) fn add_springdamper_to_multibody(
multibody_joints: &mut MultibodyJointSet,
bodies: &RigidBodySet,
handle: MultibodyJointHandle,
timeconst: Real,
dampratio: Real,
rest: Real,
) {
use rapier3d::dynamics::JointAxesMask;
use rapier3d::math::SPATIAL_DIM;
let Some((multibody, link_id)) = multibody_joints.get_mut(handle) else {
return;
};
let invweight = multibody.dof_inverse_inertia(bodies);
let mut offset = 0;
let mut ndofs = 0;
for (i, link) in multibody.links().enumerate() {
let link_ndofs = link.joint().ndofs();
if i == link_id {
ndofs = link_ndofs;
break;
}
offset += link_ndofs;
}
if ndofs == 0 {
return;
}
let eps = 1.0e-9;
let sum_inv: Real = (0..ndofs)
.map(|d| invweight.get(offset + d).copied().unwrap_or(0.0))
.sum();
let inertia = if sum_inv > eps {
ndofs as Real / sum_inv
} else {
0.0
};
let stiffness = inertia / (timeconst * timeconst * dampratio * dampratio).max(eps);
let damping = 2.0 * inertia / timeconst.max(eps);
if let Some(link) = multibody.links_mut().nth(link_id) {
let locked_bits = link.joint.data.locked_axes.bits();
for axis in 0..SPATIAL_DIM {
if (locked_bits & (1 << axis)) == 0 {
link.joint.set_spring(axis, stiffness, rest);
if let Some(flag) = JointAxesMask::from_bits(1u8 << axis) {
link.joint.data.motor_axes.remove(flag);
}
}
}
}
let damping_vec = multibody.damping_mut();
for d in 0..ndofs {
let idx = offset + d;
if idx < damping_vec.len() {
damping_vec[idx] = damping;
}
}
}
fn single_free_axis(multibody: &Multibody, link_id: usize) -> Option<(usize, usize)> {
use rapier3d::math::SPATIAL_DIM;
let link = multibody.links().nth(link_id)?;
let locked = link.joint.data.locked_axes.bits();
for axis in 0..SPATIAL_DIM {
if (locked & (1 << axis)) == 0 {
return Some((0, axis));
}
}
None
}
pub(super) fn add_joint_coupling_to_multibody(
multibody_joints: &mut MultibodyJointSet,
handle1: MultibodyJointHandle,
child1: RigidBodyHandle,
child2: RigidBodyHandle,
coeff: Real,
offset: Real,
) {
let (Some(l1), Some(l2)) = (
multibody_joints.rigid_body_link(child1).copied(),
multibody_joints.rigid_body_link(child2).copied(),
) else {
return;
};
if l1.multibody != l2.multibody {
log::warn!(
"<equality><joint>: joint1 and joint2 are in different multibodies; \
cross-multibody coupling is unsupported, skipping"
);
return;
}
let link2_id = l2.id;
let Some((multibody, link1_id)) = multibody_joints.get_mut(handle1) else {
return;
};
let (Some((dof1, axis1)), Some((dof2, axis2))) = (
single_free_axis(multibody, link1_id),
single_free_axis(multibody, link2_id),
) else {
log::warn!("<equality><joint>: a coupled joint has no free DoF; skipping");
return;
};
multibody.add_dof_coupling(MultibodyDofCoupling {
link1: link1_id,
dof1,
axis1,
link2: link2_id,
dof2,
axis2,
coeff,
offset,
});
}