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