aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSébastien Crozet <developer@crozet.re>2022-02-20 12:55:00 +0100
committerSébastien Crozet <sebastien@crozet.re>2022-03-20 21:49:16 +0100
commitfb20d72ee29de9311a81aec6eb9f02fd2aa35fc4 (patch)
tree45827ac4c754c3670d1ddb2f91fc498515d6b3b8 /src
parente740493b980dc9856864ead3206a4fa02aff965f (diff)
downloadrapier-fb20d72ee29de9311a81aec6eb9f02fd2aa35fc4.tar.gz
rapier-fb20d72ee29de9311a81aec6eb9f02fd2aa35fc4.tar.bz2
rapier-fb20d72ee29de9311a81aec6eb9f02fd2aa35fc4.zip
Joint API and joint motors improvements
Diffstat (limited to 'src')
-rw-r--r--src/dynamics/integration_parameters.rs29
-rw-r--r--src/dynamics/island_manager.rs32
-rw-r--r--src/dynamics/joint/fixed_joint.rs93
-rw-r--r--src/dynamics/joint/generic_joint.rs501
-rw-r--r--src/dynamics/joint/impulse_joint/impulse_joint.rs4
-rw-r--r--src/dynamics/joint/impulse_joint/impulse_joint_set.rs4
-rw-r--r--src/dynamics/joint/joint_data.rs275
-rw-r--r--src/dynamics/joint/mod.rs12
-rw-r--r--src/dynamics/joint/motor_model.rs49
-rw-r--r--src/dynamics/joint/multibody_joint/multibody_joint.rs40
-rw-r--r--src/dynamics/joint/multibody_joint/multibody_joint_set.rs4
-rw-r--r--src/dynamics/joint/multibody_joint/unit_multibody_joint.rs27
-rw-r--r--src/dynamics/joint/prismatic_joint.rs188
-rw-r--r--src/dynamics/joint/revolute_joint.rs171
-rw-r--r--src/dynamics/joint/spherical_joint.rs140
-rw-r--r--src/dynamics/rigid_body.rs6
-rw-r--r--src/dynamics/rigid_body_set.rs3
-rw-r--r--src/dynamics/solver/generic_velocity_constraint.rs2
-rw-r--r--src/dynamics/solver/generic_velocity_ground_constraint.rs2
-rw-r--r--src/dynamics/solver/island_solver.rs2
-rw-r--r--src/dynamics/solver/joint_constraint/joint_generic_velocity_constraint.rs151
-rw-r--r--src/dynamics/solver/joint_constraint/joint_generic_velocity_constraint_builder.rs98
-rw-r--r--src/dynamics/solver/joint_constraint/joint_velocity_constraint.rs253
-rw-r--r--src/dynamics/solver/joint_constraint/joint_velocity_constraint_builder.rs405
-rw-r--r--src/dynamics/solver/velocity_constraint.rs18
-rw-r--r--src/dynamics/solver/velocity_constraint_wide.rs14
-rw-r--r--src/dynamics/solver/velocity_ground_constraint.rs18
-rw-r--r--src/dynamics/solver/velocity_ground_constraint_wide.rs12
-rw-r--r--src/geometry/collider.rs6
-rw-r--r--src/geometry/collider_set.rs6
30 files changed, 1796 insertions, 769 deletions
diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs
index 9e3f686..bab0fe2 100644
--- a/src/dynamics/integration_parameters.rs
+++ b/src/dynamics/integration_parameters.rs
@@ -18,10 +18,6 @@ pub struct IntegrationParameters {
/// to numerical instabilities.
pub min_ccd_dt: Real,
- /// 0-1: how much of the velocity to dampen out in the constraint solver?
- /// (default `1.0`).
- pub velocity_solve_fraction: Real,
-
/// 0-1: multiplier for how much of the constraint violation (e.g. contact penetration)
/// will be compensated for during the velocity solve.
/// If zero, you need to enable the positional solver.
@@ -35,6 +31,9 @@ pub struct IntegrationParameters {
/// (default `0.25`).
pub damping_ratio: Real,
+ pub joint_erp: Real,
+ pub joint_damping_ratio: Real,
+
/// Amount of penetration the engine wont attempt to correct (default: `0.001m`).
pub allowed_linear_error: Real,
/// The maximal distance separating two objects that will generate predictive contacts (default: `0.002`).
@@ -89,12 +88,17 @@ impl IntegrationParameters {
/// The ERP coefficient, multiplied by the inverse timestep length.
pub fn erp_inv_dt(&self) -> Real {
- 0.8 / self.dt
+ self.erp / self.dt
+ }
+
+ /// The joint ERP coefficient, multiplied by the inverse timestep length.
+ pub fn joint_erp_inv_dt(&self) -> Real {
+ self.joint_erp / self.dt
}
/// The CFM factor to be used in the constraints resolution.
pub fn cfm_factor(&self) -> Real {
- // Compute CFM assuming a critically damped spring multiplied by the dampingratio.
+ // Compute CFM assuming a critically damped spring multiplied by the damping ratio.
let inv_erp_minus_one = 1.0 / self.erp - 1.0;
// let stiffness = 4.0 * damping_ratio * damping_ratio * projected_mass
@@ -124,6 +128,16 @@ impl IntegrationParameters {
// in the constraints solver.
1.0 / (1.0 + cfm_coeff)
}
+
+ pub fn joint_cfm_coeff(&self) -> Real {
+ // Compute CFM assuming a critically damped spring multiplied by the damping ratio.
+ let inv_erp_minus_one = 1.0 / self.joint_erp - 1.0;
+ inv_erp_minus_one * inv_erp_minus_one
+ / ((1.0 + inv_erp_minus_one)
+ * 4.0
+ * self.joint_damping_ratio
+ * self.joint_damping_ratio)
+ }
}
impl Default for IntegrationParameters {
@@ -131,9 +145,10 @@ impl Default for IntegrationParameters {
Self {
dt: 1.0 / 60.0,
min_ccd_dt: 1.0 / 60.0 / 100.0,
- velocity_solve_fraction: 1.0,
erp: 0.8,
damping_ratio: 0.25,
+ joint_erp: 1.0,
+ joint_damping_ratio: 1.0,
allowed_linear_error: 0.001, // 0.005
prediction_distance: 0.002,
max_velocity_iterations: 4,
diff --git a/src/dynamics/island_manager.rs b/src/dynamics/island_manager.rs
index 631cf7a..7cf16d6 100644
--- a/src/dynamics/island_manager.rs
+++ b/src/dynamics/island_manager.rs
@@ -136,36 +136,6 @@ impl IslandManager {
.chain(self.active_kinematic_set.iter().copied())
}
- /*
- #[cfg(feature = "parallel")]
- #[inline(always)]
- #[allow(dead_code)]
- pub(crate) fn foreach_active_island_body_mut_internal_parallel<Set>(
- &self,
- island_id: usize,
- bodies: &mut Set,
- f: impl Fn(RigidBodyHandle, &mut RigidBody) + Send + Sync,
- ) where
- Set: ComponentSet<T>,
- {
- use std::sync::atomic::Ordering;
-
- let island_range = self.active_islands[island_id]..self.active_islands[island_id + 1];
- let bodies = std::sync::atomic::AtomicPtr::new(&mut bodies as *mut _);
- self.active_dynamic_set[island_range]
- .par_iter()
- .for_each_init(
- || bodies.load(Ordering::Relaxed),
- |bodies, handle| {
- let bodies: &mut Set = unsafe { std::mem::transmute(*bodies) };
- if let Some(rb) = bodies.get_mut_internal(handle.0) {
- f(*handle, rb)
- }
- },
- );
- }
- */
-
#[cfg(feature = "parallel")]
pub(crate) fn active_island_range(&self, island_id: usize) -> std::ops::Range<usize> {
self.active_islands[island_id]..self.active_islands[island_id + 1]
@@ -203,7 +173,7 @@ impl IslandManager {
// NOTE: the `.rev()` is here so that two successive timesteps preserve
// the order of the bodies in the `active_dynamic_set` vec. This reversal
// does not seem to affect performances nor stability. However it makes
- // debugging slightly nicer so we keep this rev.
+ // debugging slightly nicer.
for h in self.active_dynamic_set.drain(..).rev() {
let can_sleep = &mut self.can_sleep;
let stack = &mut self.stack;
diff --git a/src/dynamics/joint/fixed_joint.rs b/src/dynamics/joint/fixed_joint.rs
index c7ca904..192b7d9 100644
--- a/src/dynamics/joint/fixed_joint.rs
+++ b/src/dynamics/joint/fixed_joint.rs
@@ -1,10 +1,11 @@
-use crate::dynamics::{JointAxesMask, JointData};
+use crate::dynamics::{GenericJoint, GenericJointBuilder, JointAxesMask};
use crate::math::{Isometry, Point, Real};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
+#[repr(transparent)]
pub struct FixedJoint {
- data: JointData,
+ data: GenericJoint,
}
impl Default for FixedJoint {
@@ -14,48 +15,100 @@ impl Default for FixedJoint {
}
impl FixedJoint {
+ #[must_use]
pub fn new() -> Self {
- #[cfg(feature = "dim2")]
- let mask = JointAxesMask::X | JointAxesMask::Y | JointAxesMask::ANG_X;
- #[cfg(feature = "dim3")]
- let mask = JointAxesMask::X
- | JointAxesMask::Y
- | JointAxesMask::Z
- | JointAxesMask::ANG_X
- | JointAxesMask::ANG_Y
- | JointAxesMask::ANG_Z;
-
- let data = JointData::default().lock_axes(mask);
+ let data = GenericJointBuilder::new(JointAxesMask::LOCKED_FIXED_AXES).build();
Self { data }
}
#[must_use]
+ pub fn local_frame1(&self) -> &Isometry<Real> {
+ &self.data.local_frame1
+ }
+
+ pub fn set_local_frame1(&mut self, local_frame: Isometry<Real>) -> &mut Self {
+ self.data.set_local_frame1(local_frame);
+ self
+ }
+
+ #[must_use]
+ pub fn local_frame2(&self) -> &Isometry<Real> {
+ &self.data.local_frame2
+ }
+
+ pub fn set_local_frame2(&mut self, local_frame: Isometry<Real>) -> &mut Self {
+ self.data.set_local_frame2(local_frame);
+ self
+ }
+
+ #[must_use]
+ pub fn local_anchor1(&self) -> Point<Real> {
+ self.data.local_anchor1()
+ }
+
+ pub fn set_local_anchor1(&mut self, anchor1: Point<Real>) -> &mut Self {
+ self.data.set_local_anchor1(anchor1);
+ self
+ }
+
+ #[must_use]
+ pub fn local_anchor2(&self) -> Point<Real> {
+ self.data.local_anchor2()
+ }
+
+ pub fn set_local_anchor2(&mut self, anchor2: Point<Real>) -> &mut Self {
+ self.data.set_local_anchor2(anchor2);
+ self
+ }
+}
+
+impl Into<GenericJoint> for FixedJoint {
+ fn into(self) -> GenericJoint {
+ self.data
+ }
+}
+
+#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
+pub struct FixedJointBuilder(FixedJoint);
+
+impl FixedJointBuilder {
+ pub fn new() -> Self {
+ Self(FixedJoint::new())
+ }
+
+ #[must_use]
pub fn local_frame1(mut self, local_frame: Isometry<Real>) -> Self {
- self.data = self.data.local_frame1(local_frame);
+ self.0.set_local_frame1(local_frame);
self
}
#[must_use]
pub fn local_frame2(mut self, local_frame: Isometry<Real>) -> Self {
- self.data = self.data.local_frame2(local_frame);
+ self.0.set_local_frame2(local_frame);
self
}
#[must_use]
pub fn local_anchor1(mut self, anchor1: Point<Real>) -> Self {
- self.data = self.data.local_anchor1(anchor1);
+ self.0.set_local_anchor1(anchor1);
self
}
#[must_use]
pub fn local_anchor2(mut self, anchor2: Point<Real>) -> Self {
- self.data = self.data.local_anchor2(anchor2);
+ self.0.set_local_anchor2(anchor2);
self
}
+
+ #[must_use]
+ pub fn build(self) -> FixedJoint {
+ self.0
+ }
}
-impl Into<JointData> for FixedJoint {
- fn into(self) -> JointData {
- self.data
+impl Into<GenericJoint> for FixedJointBuilder {
+ fn into(self) -> GenericJoint {
+ self.0.into()
}
}
diff --git a/src/dynamics/joint/generic_joint.rs b/src/dynamics/joint/generic_joint.rs
new file mode 100644
index 0000000..7455b0d
--- /dev/null
+++ b/src/dynamics/joint/generic_joint.rs
@@ -0,0 +1,501 @@
+use na::SimdRealField;
+
+use crate::dynamics::solver::MotorParameters;
+use crate::dynamics::{FixedJoint, MotorModel, PrismaticJoint, RevoluteJoint};
+use crate::math::{Isometry, Point, Real, Rotation, UnitVector, Vector, SPATIAL_DIM};
+use crate::utils::WBasis;
+
+#[cfg(feature = "dim3")]
+use crate::dynamics::SphericalJoint;
+
+#[cfg(feature = "dim3")]
+bitflags::bitflags! {
+ #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+ pub struct JointAxesMask: u8 {
+ const X = 1 << 0;
+ const Y = 1 << 1;
+ const Z = 1 << 2;
+ const ANG_X = 1 << 3;
+ const ANG_Y = 1 << 4;
+ const ANG_Z = 1 << 5;
+ const LOCKED_REVOLUTE_AXES = Self::X.bits | Self::Y.bits | Self::Z.bits | Self::ANG_Y.bits | Self::ANG_Z.bits;
+ const LOCKED_PRISMATIC_AXES = Self::Y.bits | Self::Z.bits | Self::ANG_X.bits | Self::ANG_Y.bits | Self::ANG_Z.bits;
+ const LOCKED_FIXED_AXES = Self::X.bits | Self::Y.bits | Self::Z.bits | Self::ANG_X.bits | Self::ANG_Y.bits | Self::ANG_Z.bits;
+ const LOCKED_SPHERICAL_AXES = Self::X.bits | Self::Y.bits | Self::Z.bits;
+ const FREE_REVOLUTE_AXES = Self::ANG_X.bits;
+ const FREE_PRISMATIC_AXES = Self::X.bits;
+ const FREE_FIXED_AXES = 0;
+ const FREE_SPHERICAL_AXES = Self::ANG_X.bits | Self::ANG_Y.bits | Self::ANG_Z.bits;
+ }
+}
+
+#[cfg(feature = "dim2")]
+bitflags::bitflags! {
+ #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+ pub struct JointAxesMask: u8 {
+ const X = 1 << 0;
+ const Y = 1 << 1;
+ const ANG_X = 1 << 2;
+ const LOCKED_REVOLUTE_AXES = Self::X.bits | Self::Y.bits;
+ const LOCKED_PRISMATIC_AXES = Self::Y.bits | Self::ANG_X.bits;
+ const LOCKED_FIXED_AXES = Self::X.bits | Self::Y.bits | Self::ANG_X.bits;
+ const FREE_REVOLUTE_AXES = Self::ANG_X.bits;
+ const FREE_PRISMATIC_AXES = Self::X.bits;
+ const FREE_FIXED_AXES = 0;
+ }
+}
+
+#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum JointAxis {
+ X = 0,
+ Y,
+ #[cfg(feature = "dim3")]
+ Z,
+ AngX,
+ #[cfg(feature = "dim3")]
+ AngY,
+ #[cfg(feature = "dim3")]
+ AngZ,
+}
+
+impl From<JointAxis> for JointAxesMask {
+ fn from(axis: JointAxis) -> Self {
+ JointAxesMask::from_bits(1 << axis as usize).unwrap()
+ }
+}
+
+#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct JointLimits<N> {
+ pub min: N,
+ pub max: N,
+ pub impulse: N,
+}
+
+impl<N: SimdRealField<Element = Real>> Default for JointLimits<N> {
+ fn default() -> Self {
+ Self {
+ min: -N::splat(Real::MAX),
+ max: N::splat(Real::MAX),
+ impulse: N::splat(0.0),
+ }
+ }
+}
+
+#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct JointMotor {
+ pub target_vel: Real,
+ pub target_pos: Real,
+ pub stiffness: Real,
+ pub damping: Real,
+ pub max_force: Real,
+ pub impulse: Real,
+ pub model: MotorModel,
+}
+
+impl Default for JointMotor {
+ fn default() -> Self {
+ Self {
+ target_pos: 0.0,
+ target_vel: 0.0,
+ stiffness: 0.0,
+ damping: 0.0,
+ max_force: Real::MAX,
+ impulse: 0.0,
+ model: MotorModel::AccelerationBased, // VelocityBased,
+ }
+ }
+}
+
+impl JointMotor {
+ pub(crate) fn motor_params(&self, dt: Real) -> MotorParameters<Real> {
+ let (erp_inv_dt, cfm_coeff, cfm_gain) =
+ self.model
+ .combine_coefficients(dt, self.stiffness, self.damping);
+ MotorParameters {
+ erp_inv_dt,
+ cfm_coeff,
+ cfm_gain,
+ // keep_lhs,
+ target_pos: self.target_pos,
+ target_vel: self.target_vel,
+ max_impulse: self.max_force * dt,
+ }
+ }
+}
+
+#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct GenericJoint {
+ pub local_frame1: Isometry<Real>,
+ pub local_frame2: Isometry<Real>,
+ pub locked_axes: JointAxesMask,
+ pub limit_axes: JointAxesMask,
+ pub motor_axes: JointAxesMask,
+ pub limits: [JointLimits<Real>; SPATIAL_DIM],
+ pub motors: [JointMotor; SPATIAL_DIM],
+}
+
+impl Default for GenericJoint {
+ fn default() -> Self {
+ Self {
+ local_frame1: Isometry::identity(),
+ local_frame2: Isometry::identity(),
+ locked_axes: JointAxesMask::empty(),
+ limit_axes: JointAxesMask::empty(),
+ motor_axes: JointAxesMask::empty(),
+ limits: [JointLimits::default(); SPATIAL_DIM],
+ motors: [JointMotor::default(); SPATIAL_DIM],
+ }
+ }
+}
+
+impl GenericJoint {
+ #[must_use]
+ pub fn new(locked_axes: JointAxesMask) -> Self {
+ *Self::default().lock_axes(locked_axes)
+ }
+
+ /// Can this joint use SIMD-accelerated constraint formulations?
+ pub(crate) fn supports_simd_constraints(&self) -> bool {
+ self.limit_axes.is_empty() && self.motor_axes.is_empty()
+ }
+
+ fn complete_ang_frame(axis: UnitVector<Real>) -> Rotation<Real> {
+ let basis = axis.orthonormal_basis();
+
+ #[cfg(feature = "dim2")]
+ {
+ use na::{Matrix2, Rotation2, UnitComplex};
+ let mat = Matrix2::from_columns(&[axis.into_inner(), basis[0]]);
+ let rotmat = Rotation2::from_matrix_unchecked(mat);
+ UnitComplex::from_rotation_matrix(&rotmat)
+ }
+
+ #[cfg(feature = "dim3")]
+ {
+ use na::{Matrix3, Rotation3, UnitQuaternion};
+ let mat = Matrix3::from_columns(&[axis.into_inner(), basis[0], basis[1]]);
+ let rotmat = Rotation3::from_matrix_unchecked(mat);
+ UnitQuaternion::from_rotation_matrix(&rotmat)
+ }
+ }
+
+ pub fn lock_axes(&mut self, axes: JointAxesMask) -> &mut Self {
+ self.locked_axes |= axes;
+ self
+ }
+
+ pub fn set_local_frame1(&mut self, local_frame: Isometry<Real>) -> &mut Self {
+ self.local_frame1 = local_frame;
+ self
+ }
+
+ pub fn set_local_frame2(&mut self, local_frame: Isometry<Real>) -> &mut Self {
+ self.local_frame2 = local_frame;
+ self
+ }
+
+ #[must_use]
+ pub fn local_axis1(&self) -> UnitVector<Real> {
+ self.local_frame1 * Vector::x_axis()
+ }
+
+ pub fn set_local_axis1(&mut self, local_axis: UnitVector<Real>) -> &mut Self {
+ self.local_frame1.rotation = Self::complete_ang_frame(local_axis);
+ self
+ }
+
+ #[must_use]
+ pub fn local_axis2(&self) -> UnitVector<Real> {
+ self.local_frame2 * Vector::x_axis()
+ }
+
+ pub fn set_local_axis2(&mut self, local_axis: UnitVector<Real>) -> &mut Self {
+ self.local_frame2.rotation = Self::complete_ang_frame(local_axis);
+ self
+ }
+
+ #[must_use]
+ pub fn local_anchor1(&self) -> Point<Real> {
+ self.local_frame1.translation.vector.into()
+ }
+
+ pub fn set_local_anchor1(&mut self, anchor1: Point<Real>) -> &mut Self {
+ self.local_frame1.translation.vector = anchor1.coords;
+ self
+ }
+
+ #[must_use]
+ pub fn local_anchor2(&self) -> Point<Real> {
+ self.local_frame2.translation.vector.into()
+ }
+
+ pub fn set_local_anchor2(&mut self, anchor2: Point<Real>) -> &mut Self {
+ self.local_frame2.translation.vector = anchor2.coords;
+ self
+ }
+
+ #[must_use]
+ pub fn limits(&self, axis: JointAxis) -> Option<&JointLimits<Real>> {
+ let i = axis as usize;
+ if self.limit_axes.contains(axis.into()) {
+ Some(&self.limits[i])
+ } else {
+ None
+ }
+ }
+
+ pub fn set_limits(&mut self, axis: JointAxis, limits: [Real; 2]) -> &mut Self {
+ let i = axis as usize;
+ self.limit_axes |= axis.into();
+ self.limits[i].min = limits[0];
+ self.limits[i].max = limits[1];
+ self
+ }
+
+ #[must_use]
+ pub fn motor_model(&self, axis: JointAxis) -> Option<MotorModel> {
+ let i = axis as usize;
+ if self.motor_axes.contains(axis.into()) {
+ Some(self.motors[i].model)
+ } else {
+ None
+ }
+ }
+
+ /// Set the spring-like model used by the motor to reach the desired target velocity and position.
+ pub fn set_motor_model(&mut self, axis: JointAxis, model: MotorModel) -> &mut Self {
+ self.motors[axis as usize].model = model;
+ self
+ }
+
+ /// Sets the target velocity this motor needs to reach.
+ pub fn set_motor_velocity(
+ &mut self,
+ axis: JointAxis,
+ target_vel: Real,
+ factor: Real,
+ ) -> &mut Self {
+ self.set_motor(
+ axis,
+ self.motors[axis as usize].target_pos,
+ target_vel,
+ 0.0,
+ factor,
+ )
+ }
+
+ /// Sets the target angle this motor needs to reach.
+ pub fn set_motor_position(
+ &mut self,
+ axis: JointAxis,
+ target_pos: Real,
+ stiffness: Real,
+ damping: Real,
+ ) -> &mut Self {
+ self.set_motor(axis, target_pos, 0.0, stiffness, damping)
+ }
+
+ pub fn set_motor_max_force(&mut self, axis: JointAxis, max_force: Real) -> &mut Self {
+ self.motors[axis as usize].max_force = max_force;
+ self
+ }
+
+ #[must_use]
+ pub fn motor(&self, axis: JointAxis) -> Option<&JointMotor> {
+ let i = axis as usize;
+ if self.motor_axes.contains(axis.into()) {
+ Some(&self.motors[i])
+ } else {
+ None
+ }
+ }
+
+ /// Configure both the target angle and target velocity of the motor.
+ pub fn set_motor(
+ &mut self,
+ axis: JointAxis,
+ target_pos: Real,
+ target_vel: Real,
+ stiffness: Real,
+ damping: Real,
+ ) -> &mut Self {
+ self.motor_axes |= axis.into();
+ let i = axis as usize;
+ self.motors[i].target_vel = target_vel;
+ self.motors[i].target_pos = target_pos;
+ self.motors[i].stiffness = stiffness;
+ self.motors[i].damping = damping;
+ self
+ }
+}
+
+macro_rules! joint_conversion_methods(
+ ($as_joint: ident, $as_joint_mut: ident, $Joint: ty, $axes: expr) => {
+ #[must_use]
+ pub fn $as_joint(&self) -> Option<&$Joint> {
+ if self.locked_axes == $axes {
+ // SAFETY: this is OK because the target joint type is
+ // a `repr(transparent)` newtype of `Joint`.
+ Some(unsafe { std::mem::transmute(self) })
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn $as_joint_mut(&mut self) -> Option<&mut $Joint> {
+ if self.locked_axes == $axes {
+ // SAFETY: this is OK because the target joint type is
+ // a `repr(transparent)` newtype of `Joint`.
+ Some(unsafe { std::mem::transmute(self) })
+ } else {
+ None
+ }
+ }
+ }
+);
+
+impl GenericJoint {
+ joint_conversion_methods!(
+ as_revolute,
+ as_revolute_mut,
+ RevoluteJoint,
+ JointAxesMask::LOCKED_REVOLUTE_AXES
+ );
+ joint_conversion_methods!(
+ as_fixed,
+ as_fixed_mut,
+ FixedJoint,
+ JointAxesMask::LOCKED_FIXED_AXES
+ );
+ joint_conversion_methods!(
+ as_prismatic,
+ as_prismatic_mut,
+ PrismaticJoint,
+ JointAxesMask::LOCKED_PRISMATIC_AXES
+ );
+
+ #[cfg(feature = "dim3")]
+ joint_conversion_methods!(
+ as_spherical,
+ as_spherical_mut,
+ SphericalJoint,
+ JointAxesMask::LOCKED_SPHERICAL_AXES
+ );
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct GenericJointBuilder(GenericJoint);
+
+impl GenericJointBuilder {
+ #[must_use]
+ pub fn new(locked_axes: JointAxesMask) -> Self {
+ Self(GenericJoint::new(locked_axes))
+ }
+
+ #[must_use]
+ pub fn lock_axes(mut self, axes: JointAxesMask) -> Self {
+ self.0.lock_axes(axes);
+ self
+ }
+
+ #[must_use]
+ pub fn local_frame1(mut self, local_frame: Isometry<Real>) -> Self {
+ self.0.set_local_frame1(local_frame);
+ self
+ }
+
+ #[must_use]
+ pub fn local_frame2(mut self, local_frame: Isometry<Real>) -> Self {
+ self.0.set_local_frame2(local_frame);
+ self
+ }
+
+ #[must_use]
+ pub fn local_axis1(mut self, local_axis: UnitVector<Real>) -> Self {
+ self.0.set_local_axis1(local_axis);
+ self
+ }
+
+ #[must_use]
+ pub fn local_axis2(mut self, local_axis: UnitVector<Real>) -> Self {
+ self.0.set_local_axis2(local_axis);
+ self
+ }
+
+ #[must_use]
+ pub fn local_anchor1(mut self, anchor1: Point<Real>) -> Self {
+ self.0.set_local_anchor1(anchor1);
+ self
+ }
+
+ #[must_use]
+ pub fn local_anchor2(mut self, anchor2: Point<Real>) -> Self {
+ self.0.set_local_anchor2(anchor2);
+ self
+ }
+
+ #[must_use]
+ pub fn limits(mut self, axis: JointAxis, limits: [Real; 2]) -> Self {
+ self.0.set_limits(axis, limits);
+ self
+ }
+
+ /// Set the spring-like model used by the motor to reach the desired target velocity and position.
+ #[must_use]
+ pub fn motor_model(mut self, axis: JointAxis, model: MotorModel) -> Self {
+ self.0.set_motor_model(axis, model);
+ self
+ }
+
+ /// Sets the target velocity this motor needs to reach.
+ #[must_use]
+ pub fn motor_velocity(mut self, axis: JointAxis, target_vel: Real, factor: Real) -> Self {
+ self.0.set_motor_velocity(axis, target_vel, factor);
+ self
+ }
+
+ /// Sets the target angle this motor needs to reach.
+ #[must_use]
+ pub fn motor_position(
+ mut self,
+ axis: JointAxis,
+ target_pos: Real,
+ stiffness: Real,
+ damping: Real,
+ ) -> Self {
+ self.0
+ .set_motor_position(axis, target_pos, stiffness, damping);
+ self
+ }
+
+ /// Configure both the target angle and target velocity of the motor.
+ #[must_use]
+ pub fn set_motor(
+ mut self,
+ axis: JointAxis,
+ target_pos: Real,
+ target_vel: Real,
+ stiffness: Real,
+ damping: Real,
+ ) -> Self {
+ self.0
+ .set_motor(axis, target_pos, target_vel, stiffness, damping);
+ self
+ }
+
+ #[must_use]
+ pub fn motor_max_force(mut self, axis: JointAxis, max_force: Real) -> Self {
+ self.0.set_motor_max_force(axis, max_force);
+ self
+ }
+
+ #[must_use]
+ pub fn build(self) -> GenericJoint {
+ self.0
+ }
+}
diff --git a/src/dynamics/joint/impulse_joint/impulse_joint.rs b/src/dynamics/joint/impulse_joint/impulse_joint.rs
index f3f4f7c..993542a 100644
--- a/src/dynamics/joint/impulse_joint/impulse_joint.rs
+++ b/src/dynamics/joint/impulse_joint/impulse_joint.rs
@@ -1,4 +1,4 @@
-use crate::dynamics::{ImpulseJointHandle, JointData, RigidBodyHandle};
+use crate::dynamics::{GenericJoint, ImpulseJointHandle, RigidBodyHandle};
use crate::math::{Real, SpacialVector};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
@@ -10,7 +10,7 @@ pub struct ImpulseJoint {
/// Handle to the second body attached to this joint.
pub body2: RigidBodyHandle,
- pub data: JointData,
+ pub data: GenericJoint,
pub impulses: SpacialVector<Real>,
// A joint needs to know its handle to simplify its removal.
diff --git a/src/dynamics/joint/impulse_joint/impulse_joint_set.rs b/src/dynamics/joint/impulse_joint/impulse_joint_set.rs
index 51d5989..8677772 100644
--- a