Skip to main content

rapier2d/dynamics/joint/multibody_joint/
unit_multibody_joint.rs

1#![allow(missing_docs)] // For downcast.
2
3use crate::dynamics::integration_parameters::SpringCoefficients;
4use crate::dynamics::joint::MultibodyLink;
5use crate::dynamics::solver::{GenericJointConstraint, WritebackId};
6use crate::dynamics::{IntegrationParameters, JointMotor, Multibody};
7use crate::math::{DVector, Real};
8
9/// Initializes and generate the velocity constraints applicable to the multibody links attached
10/// to this multibody_joint.
11pub fn unit_joint_limit_constraint(
12    params: &IntegrationParameters,
13    multibody: &Multibody,
14    link: &MultibodyLink,
15    limits: [Real; 2],
16    curr_pos: Real,
17    dof_id: usize,
18    j_id: &mut usize,
19    jacobians: &mut DVector,
20    constraints: &mut [GenericJointConstraint],
21    insert_at: &mut usize,
22    softness: SpringCoefficients<Real>,
23) {
24    let ndofs = multibody.ndofs();
25    let min_enabled = curr_pos < limits[0];
26    let max_enabled = limits[1] < curr_pos;
27
28    // Compute per-joint ERP and CFM
29    let erp_inv_dt = softness.erp_inv_dt(params.dt);
30    let cfm_coeff = softness.cfm_coeff(params.dt);
31
32    let rhs_bias = ((curr_pos - limits[1]).max(0.0) - (limits[0] - curr_pos).max(0.0)) * erp_inv_dt;
33    let rhs_wo_bias = 0.0;
34
35    let dof_j_id = *j_id + dof_id + link.assembly_id;
36    jacobians.rows_mut(*j_id, ndofs * 2).fill(0.0);
37    jacobians[dof_j_id] = 1.0;
38    jacobians[dof_j_id + ndofs] = 1.0;
39    multibody
40        .inv_augmented_mass()
41        .solve_mut(&mut jacobians.rows_mut(*j_id + ndofs, ndofs));
42
43    let lhs = jacobians[dof_j_id + ndofs]; // = J^t * M^-1 J
44    let impulse_bounds = [
45        min_enabled as u32 as Real * -Real::MAX,
46        max_enabled as u32 as Real * Real::MAX,
47    ];
48    let cfm_gain = lhs * cfm_coeff;
49
50    let constraint = GenericJointConstraint {
51        is_rigid_body1: false,
52        solver_vel1: u32::MAX,
53        ndofs1: 0,
54        j_id1: 0,
55
56        is_rigid_body2: false,
57        solver_vel2: multibody.solver_id,
58        ndofs2: ndofs,
59        j_id2: *j_id,
60        joint_id: usize::MAX, // TODO: we don’t support impulse writeback for internal constraints yet.
61        impulse: 0.0,
62        impulse_bounds,
63        inv_lhs: crate::utils::inv(lhs + cfm_gain),
64        rhs: rhs_wo_bias + rhs_bias,
65        rhs_wo_bias,
66        cfm_coeff,
67        cfm_gain,
68        writeback_id: WritebackId::Limit(dof_id),
69    };
70
71    constraints[*insert_at] = constraint;
72    *insert_at += 1;
73
74    *j_id += 2 * ndofs;
75}
76
77/// Generates the dry-friction (MuJoCo `frictionloss`) velocity constraint for
78/// one generalized DoF.
79///
80/// `softness` supplies the CFM compliance (MuJoCo's `solreffriction`); only CFM
81/// applies, never ERP, since there is no position error for a bias to chase.
82#[allow(clippy::too_many_arguments)]
83pub fn unit_joint_friction_constraint(
84    params: &IntegrationParameters,
85    multibody: &Multibody,
86    link: &MultibodyLink,
87    friction: Real,
88    dof_id: usize,
89    j_id: &mut usize,
90    jacobians: &mut DVector,
91    constraints: &mut [GenericJointConstraint],
92    insert_at: &mut usize,
93    softness: SpringCoefficients<Real>,
94) {
95    let ndofs = multibody.ndofs();
96    let cfm_coeff = softness.cfm_coeff(params.dt);
97
98    let dof_j_id = *j_id + dof_id + link.assembly_id;
99    jacobians.rows_mut(*j_id, ndofs * 2).fill(0.0);
100    jacobians[dof_j_id] = 1.0;
101    jacobians[dof_j_id + ndofs] = 1.0;
102    multibody
103        .inv_augmented_mass()
104        .solve_mut(&mut jacobians.rows_mut(*j_id + ndofs, ndofs));
105
106    let lhs = jacobians[dof_j_id + ndofs]; // = J^t * M^-1 J
107    let max_impulse = friction * params.dt;
108    let cfm_gain = lhs * cfm_coeff;
109
110    let constraint = GenericJointConstraint {
111        is_rigid_body1: false,
112        solver_vel1: u32::MAX,
113        ndofs1: 0,
114        j_id1: 0,
115        is_rigid_body2: false,
116        solver_vel2: multibody.solver_id,
117        ndofs2: ndofs,
118        j_id2: *j_id,
119        joint_id: usize::MAX, // TODO: we don’t support impulse writeback for internal constraints yet.
120        impulse: 0.0,
121        impulse_bounds: [-max_impulse, max_impulse],
122        inv_lhs: crate::utils::inv(lhs + cfm_gain),
123        rhs: 0.0,
124        rhs_wo_bias: 0.0,
125        cfm_coeff,
126        cfm_gain,
127        writeback_id: WritebackId::Friction(dof_id),
128    };
129
130    constraints[*insert_at] = constraint;
131    *insert_at += 1;
132
133    *j_id += 2 * ndofs;
134}
135
136/// Initializes and generate the velocity constraints applicable to the multibody links attached
137/// to this multibody_joint.
138pub fn unit_joint_motor_constraint(
139    params: &IntegrationParameters,
140    multibody: &Multibody,
141    link: &MultibodyLink,
142    motor: &JointMotor,
143    curr_pos: Real,
144    limits: Option<[Real; 2]>,
145    dof_id: usize,
146    j_id: &mut usize,
147    jacobians: &mut DVector,
148    constraints: &mut [GenericJointConstraint],
149    insert_at: &mut usize,
150) {
151    let inv_dt = params.inv_dt();
152    let ndofs = multibody.ndofs();
153    let motor_params = motor.motor_params(params.dt);
154
155    let dof_j_id = *j_id + dof_id + link.assembly_id;
156    jacobians.rows_mut(*j_id, ndofs * 2).fill(0.0);
157    jacobians[dof_j_id] = 1.0;
158    jacobians[dof_j_id + ndofs] = 1.0;
159    multibody
160        .inv_augmented_mass()
161        .solve_mut(&mut jacobians.rows_mut(*j_id + ndofs, ndofs));
162
163    let lhs = jacobians[dof_j_id + ndofs]; // = J^t * M^-1 J
164    let impulse_bounds = [-motor_params.max_impulse, motor_params.max_impulse];
165    let cfm_gain = lhs * motor_params.cfm_coeff + motor_params.cfm_gain;
166
167    let mut rhs_wo_bias = 0.0;
168    if motor_params.erp_inv_dt != 0.0 {
169        rhs_wo_bias += (curr_pos - motor_params.target_pos) * motor_params.erp_inv_dt;
170    }
171
172    let mut target_vel = motor_params.target_vel;
173    if let Some(limits) = limits {
174        target_vel = target_vel.clamp(
175            (limits[0] - curr_pos) * inv_dt,
176            (limits[1] - curr_pos) * inv_dt,
177        );
178    };
179
180    rhs_wo_bias += -target_vel;
181
182    let constraint = GenericJointConstraint {
183        is_rigid_body1: false,
184        solver_vel1: u32::MAX,
185        ndofs1: 0,
186        j_id1: 0,
187
188        is_rigid_body2: false,
189        solver_vel2: multibody.solver_id,
190        ndofs2: ndofs,
191        j_id2: *j_id,
192        joint_id: usize::MAX, // TODO: we don’t support impulse writeback for internal constraints yet.
193        impulse: 0.0,
194        impulse_bounds,
195        cfm_coeff: motor_params.cfm_coeff,
196        cfm_gain,
197        inv_lhs: crate::utils::inv(lhs + cfm_gain),
198        rhs: rhs_wo_bias,
199        rhs_wo_bias,
200        writeback_id: WritebackId::Limit(dof_id),
201    };
202
203    constraints[*insert_at] = constraint;
204    *insert_at += 1;
205
206    *j_id += 2 * ndofs;
207}