rapier3d/dynamics/solver/
velocity_solver.rs

1use crate::dynamics::solver::JointConstraintsSet;
2use crate::dynamics::solver::contact_constraint::ContactConstraintsSet;
3use crate::dynamics::solver::solver_body::SolverBodies;
4use crate::dynamics::{
5    IntegrationParameters, IslandManager, JointGraphEdge, JointIndex, MultibodyJointSet,
6    MultibodyLinkId, RigidBodySet, RigidBodyType, solver::SolverVel,
7};
8use crate::geometry::{ContactManifold, ContactManifoldIndex};
9use crate::math::{DVector, Real};
10use crate::prelude::RigidBodyVelocity;
11use parry::math::SIMD_WIDTH;
12
13#[cfg(feature = "dim3")]
14use crate::dynamics::FrictionModel;
15
16pub(crate) struct VelocitySolver {
17    pub solver_bodies: SolverBodies,
18    pub solver_vels_increment: Vec<SolverVel<Real>>,
19    pub generic_solver_vels: DVector,
20    pub generic_solver_vels_increment: DVector,
21    pub multibody_roots: Vec<MultibodyLinkId>,
22}
23
24impl VelocitySolver {
25    pub fn new() -> Self {
26        Self {
27            solver_bodies: SolverBodies::default(),
28            solver_vels_increment: Vec::new(),
29            generic_solver_vels: DVector::zeros(0),
30            generic_solver_vels_increment: DVector::zeros(0),
31            multibody_roots: Vec::new(),
32        }
33    }
34
35    pub fn init_constraints(
36        &self,
37        island_id: usize,
38        islands: &IslandManager,
39        bodies: &mut RigidBodySet,
40        multibodies: &mut MultibodyJointSet,
41        manifolds_all: &mut [&mut ContactManifold],
42        manifold_indices: &[ContactManifoldIndex],
43        joints_all: &mut [JointGraphEdge],
44        joint_indices: &[JointIndex],
45        contact_constraints: &mut ContactConstraintsSet,
46        joint_constraints: &mut JointConstraintsSet,
47        #[cfg(feature = "dim3")] friction_model: FrictionModel,
48    ) {
49        contact_constraints.init(
50            island_id,
51            islands,
52            bodies,
53            &self.solver_bodies,
54            multibodies,
55            manifolds_all,
56            manifold_indices,
57            #[cfg(feature = "dim3")]
58            friction_model,
59        );
60
61        joint_constraints.init(
62            island_id,
63            islands,
64            bodies,
65            multibodies,
66            joints_all,
67            joint_indices,
68        );
69    }
70
71    pub fn init_solver_velocities_and_solver_bodies(
72        &mut self,
73        total_step_dt: Real,
74        params: &IntegrationParameters,
75        island_id: usize,
76        islands: &IslandManager,
77        bodies: &mut RigidBodySet,
78        multibodies: &mut MultibodyJointSet,
79    ) {
80        self.multibody_roots.clear();
81        self.solver_bodies.clear();
82
83        let aligned_solver_bodies_len =
84            islands.island(island_id).len().div_ceil(SIMD_WIDTH) * SIMD_WIDTH;
85        self.solver_bodies.resize(aligned_solver_bodies_len);
86
87        self.solver_vels_increment.clear();
88        self.solver_vels_increment
89            .resize(aligned_solver_bodies_len, SolverVel::zero());
90
91        /*
92         * Initialize solver bodies and delta-velocities (`solver_vels_increment`) with external forces (gravity etc):
93         * NOTE: we compute this only once by neglecting changes of mass matrices.
94         */
95
96        // Assign solver ids to multibodies, and collect the relevant roots.
97        // And init solver_vels for rigid-bodies.
98        let mut multibody_solver_id = 0;
99
100        for (offset, handle) in islands.island(island_id).bodies().iter().enumerate() {
101            if let Some(link) = multibodies.rigid_body_link(*handle).copied() {
102                let multibody = multibodies
103                    .get_multibody_mut_internal(link.multibody)
104                    .unwrap();
105
106                if link.id == 0 || link.id == 1 && !multibody.root_is_dynamic {
107                    multibody.solver_id = multibody_solver_id;
108                    multibody_solver_id += multibody.ndofs() as u32;
109                    self.multibody_roots.push(link);
110                }
111            } else {
112                let rb = &bodies[*handle];
113                assert_eq!(offset, rb.ids.active_set_id);
114                let solver_vel_incr = &mut self.solver_vels_increment[rb.ids.active_set_id];
115                self.solver_bodies
116                    .copy_from(total_step_dt, rb.ids.active_set_id, rb);
117
118                solver_vel_incr.angular =
119                    rb.mprops.effective_world_inv_inertia * rb.forces.torque * params.dt;
120                solver_vel_incr.linear = rb.forces.force * rb.mprops.effective_inv_mass * params.dt;
121            }
122        }
123
124        // TODO PERF: don’t reallocate at each iteration.
125        self.generic_solver_vels_increment = DVector::zeros(multibody_solver_id as usize);
126        self.generic_solver_vels = DVector::zeros(multibody_solver_id as usize);
127
128        // init solver_vels for multibodies.
129        for link in &self.multibody_roots {
130            let multibody = multibodies
131                .get_multibody_mut_internal(link.multibody)
132                .unwrap();
133            multibody.update_dynamics(params.dt, bodies);
134            multibody.update_acceleration(bodies);
135
136            let mut solver_vels_incr = self
137                .generic_solver_vels_increment
138                .rows_mut(multibody.solver_id as usize, multibody.ndofs());
139            let mut solver_vels = self
140                .generic_solver_vels
141                .rows_mut(multibody.solver_id as usize, multibody.ndofs());
142
143            solver_vels_incr.axpy(params.dt, &multibody.accelerations, 0.0);
144            solver_vels.copy_from(&multibody.velocities);
145        }
146    }
147
148    #[profiling::function]
149    pub fn solve_constraints(
150        &mut self,
151        params: &IntegrationParameters,
152        num_substeps: usize,
153        bodies: &mut RigidBodySet,
154        multibodies: &mut MultibodyJointSet,
155        contact_constraints: &mut ContactConstraintsSet,
156        joint_constraints: &mut JointConstraintsSet,
157    ) {
158        for substep_id in 0..num_substeps {
159            let is_last_substep = substep_id == num_substeps - 1;
160
161            // TODO PERF: could easily use SIMD.
162            for (solver_vels, incr) in self
163                .solver_bodies
164                .vels
165                .iter_mut()
166                .zip(self.solver_vels_increment.iter())
167            {
168                solver_vels.linear += incr.linear;
169                solver_vels.angular += incr.angular;
170            }
171
172            self.generic_solver_vels += &self.generic_solver_vels_increment;
173
174            /*
175             * Update & solve constraints with bias.
176             */
177            joint_constraints.update(params, multibodies, &self.solver_bodies);
178            contact_constraints.update(params, substep_id, multibodies, &self.solver_bodies);
179
180            if params.warmstart_coefficient != 0.0 {
181                // TODO PERF: we could probably figure out a way to avoid this warmstart when
182                //            step_id > 0? Maybe for that to happen `solver_vels` needs to
183                //            represent velocity changes instead of total rigid-body velocities.
184                //            Need to be careful wrt. multibody and joints too.
185                contact_constraints
186                    .warmstart(&mut self.solver_bodies, &mut self.generic_solver_vels);
187            }
188
189            for _ in 0..params.num_internal_pgs_iterations {
190                joint_constraints.solve(&mut self.solver_bodies, &mut self.generic_solver_vels);
191                contact_constraints.solve(&mut self.solver_bodies, &mut self.generic_solver_vels);
192            }
193
194            /*
195             * Integrate positions.
196             */
197            self.integrate_positions(params, is_last_substep, bodies, multibodies);
198
199            /*
200             * Resolution without bias.
201             */
202            for _ in 0..params.num_internal_stabilization_iterations {
203                joint_constraints
204                    .solve_wo_bias(&mut self.solver_bodies, &mut self.generic_solver_vels);
205                contact_constraints
206                    .solve_wo_bias(&mut self.solver_bodies, &mut self.generic_solver_vels);
207            }
208        }
209    }
210
211    #[profiling::function]
212    pub fn integrate_positions(
213        &mut self,
214        params: &IntegrationParameters,
215        is_last_substep: bool,
216        bodies: &mut RigidBodySet,
217        multibodies: &mut MultibodyJointSet,
218    ) {
219        for (solver_vels, solver_pose) in self
220            .solver_bodies
221            .vels
222            .iter()
223            .zip(self.solver_bodies.poses.iter_mut())
224        {
225            let linvel = solver_vels.linear;
226            let angvel = solver_vels.angular;
227
228            // TODO: should we add a compile flag (or a simulation parameter)
229            //       to disable the rotation linearization?
230            let new_vels = RigidBodyVelocity { linvel, angvel };
231            new_vels.integrate_linearized(
232                params.dt,
233                &mut solver_pose.translation,
234                &mut solver_pose.rotation,
235            );
236        }
237
238        // TODO PERF: SIMD-optimized integration. Works fine, but doesn’t run faster than the scalar
239        //            one (tested on Apple Silicon/Neon, might be worth double-checking on x86_64/SSE2).
240        // // SAFETY: this assertion ensures the unchecked gathers are sound.
241        // assert_eq!(self.solver_bodies.len() % SIMD_WIDTH, 0);
242        // let dt = SimdReal::splat(params.dt);
243        // for i in (0..self.solver_bodies.len()).step_by(SIMD_WIDTH) {
244        //     let idx = [i, i + 1, i + 2, i + 3];
245        //     let solver_vels = unsafe { self.solver_bodies.gather_vels_unchecked(idx) };
246        //     let mut solver_poses = unsafe { self.solver_bodies.gather_poses_unchecked(idx) };
247        //     // let solver_consts = unsafe { self.solver_bodies.gather_consts_unchecked(idx) };
248        //
249        //     let linvel = solver_vels.linear;
250        //     let angvel = solver_poses.ii_sqrt.transform_vector(solver_vels.angular);
251        //
252        //     let mut new_vels = RigidBodyVelocity { linvel, angvel };
253        //     // TODO: store the post-damping velocity?
254        //     // new_vels = new_vels.apply_damping(dt, &solver_consts.damping);
255        //     new_vels.integrate_linearized(dt, &mut solver_poses.pose);
256        //     self.solver_bodies
257        //         .scatter_poses_unchecked(idx, solver_poses);
258        // }
259
260        // Integrate multibody positions.
261        for link in &self.multibody_roots {
262            let multibody = multibodies
263                .get_multibody_mut_internal(link.multibody)
264                .unwrap();
265            let solver_vels = self
266                .generic_solver_vels
267                .rows(multibody.solver_id as usize, multibody.ndofs());
268            multibody.velocities.copy_from(&solver_vels);
269            multibody.integrate(params.dt);
270            // PERF: don’t write back to the rigid-body poses `bodies` before the last step?
271            multibody.forward_kinematics(bodies, false);
272            multibody.update_rigid_bodies_internal(bodies, !is_last_substep, true, false);
273
274            if !is_last_substep {
275                // These are very expensive and not needed if we don’t
276                // have to run another step.
277                multibody.update_dynamics(params.dt, bodies);
278                multibody.update_acceleration(bodies);
279
280                let mut solver_vels_incr = self
281                    .generic_solver_vels_increment
282                    .rows_mut(multibody.solver_id as usize, multibody.ndofs());
283                solver_vels_incr.axpy(params.dt, &multibody.accelerations, 0.0);
284            }
285        }
286    }
287
288    pub fn writeback_bodies(
289        &mut self,
290        params: &IntegrationParameters,
291        islands: &IslandManager,
292        island_id: usize,
293        bodies: &mut RigidBodySet,
294        multibodies: &mut MultibodyJointSet,
295    ) {
296        for handle in islands.island(island_id).bodies() {
297            let link = if self.multibody_roots.is_empty() {
298                None
299            } else {
300                multibodies.rigid_body_link(*handle).copied()
301            };
302
303            if let Some(link) = link {
304                let multibody = multibodies
305                    .get_multibody_mut_internal(link.multibody)
306                    .unwrap();
307
308                if link.id == 0 || link.id == 1 && !multibody.root_is_dynamic {
309                    let solver_vels = self
310                        .generic_solver_vels
311                        .rows(multibody.solver_id as usize, multibody.ndofs());
312                    multibody.velocities.copy_from(&solver_vels);
313                }
314            } else {
315                let rb = bodies.index_mut_internal(*handle);
316                let solver_vels = &self.solver_bodies.vels[rb.ids.active_set_id];
317                let solver_poses = &self.solver_bodies.poses[rb.ids.active_set_id];
318
319                let dangvel = solver_vels.angular;
320
321                let mut new_vels = RigidBodyVelocity {
322                    linvel: solver_vels.linear,
323                    angvel: dangvel,
324                };
325                new_vels = new_vels.apply_damping(params.dt, &rb.damping);
326
327                rb.vels = new_vels;
328
329                // NOTE: if it's a position-based kinematic body, don't writeback as we want
330                //       to preserve exactly the value given by the user (it might not be exactly
331                //       equal to the integrated position because of rounding errors).
332                if rb.body_type != RigidBodyType::KinematicPositionBased {
333                    let local_com = -rb.mprops.local_mprops.local_com;
334                    rb.pos.next_position = solver_poses.pose().prepend_translation(local_com);
335                }
336
337                if rb.ccd.ccd_enabled {
338                    // TODO: Is storing this still necessary instead of just recomputing it
339                    //       during CCD?
340                    rb.ccd_vels = rb
341                        .pos
342                        .interpolate_velocity(params.inv_dt(), rb.local_center_of_mass());
343                } else {
344                    rb.ccd_vels = RigidBodyVelocity::zero();
345                }
346            }
347        }
348    }
349}