rapier3d/dynamics/solver/
interaction_groups.rs

1use crate::dynamics::{IslandManager, JointGraphEdge, JointIndex, RigidBodySet};
2use crate::geometry::{ContactManifold, ContactManifoldIndex};
3
4#[cfg(feature = "simd-is-enabled")]
5use {
6    crate::math::{SIMD_LAST_INDEX, SIMD_WIDTH},
7    vec_map::VecMap,
8};
9
10#[cfg(feature = "parallel")]
11use crate::dynamics::{MultibodyJointSet, RigidBodyHandle};
12
13#[cfg(feature = "parallel")]
14pub(crate) trait PairInteraction {
15    fn body_pair(&self) -> (Option<RigidBodyHandle>, Option<RigidBodyHandle>);
16}
17#[cfg(feature = "simd-is-enabled")]
18use crate::dynamics::RigidBodyType;
19
20#[cfg(feature = "parallel")]
21impl PairInteraction for &mut ContactManifold {
22    fn body_pair(&self) -> (Option<RigidBodyHandle>, Option<RigidBodyHandle>) {
23        (self.data.rigid_body1, self.data.rigid_body2)
24    }
25}
26
27#[cfg(feature = "parallel")]
28impl PairInteraction for JointGraphEdge {
29    fn body_pair(&self) -> (Option<RigidBodyHandle>, Option<RigidBodyHandle>) {
30        (Some(self.weight.body1), Some(self.weight.body2))
31    }
32}
33
34#[cfg(feature = "parallel")]
35#[allow(dead_code)] // That will likely be useful when we re-introduce intra-island parallelism.
36pub(crate) struct ParallelInteractionGroups {
37    bodies_color: Vec<u128>,         // Workspace.
38    interaction_indices: Vec<usize>, // Workspace.
39    interaction_colors: Vec<usize>,  // Workspace.
40    sorted_interactions: Vec<usize>,
41    groups: Vec<usize>,
42}
43
44#[cfg(feature = "parallel")]
45#[allow(dead_code)] // That will likely be useful when we re-introduce intra-island parallelism.
46impl ParallelInteractionGroups {
47    pub fn new() -> Self {
48        Self {
49            bodies_color: Vec::new(),
50            interaction_indices: Vec::new(),
51            interaction_colors: Vec::new(),
52            sorted_interactions: Vec::new(),
53            groups: Vec::new(),
54        }
55    }
56
57    pub fn group(&self, i: usize) -> &[usize] {
58        let range = self.groups[i]..self.groups[i + 1];
59        &self.sorted_interactions[range]
60    }
61
62    pub fn num_groups(&self) -> usize {
63        self.groups.len().saturating_sub(1)
64    }
65
66    pub fn group_interactions<Interaction: PairInteraction>(
67        &mut self,
68        island_id: usize,
69        islands: &IslandManager,
70        bodies: &RigidBodySet,
71        multibodies: &MultibodyJointSet,
72        interactions: &[Interaction],
73        interaction_indices: &[usize],
74    ) {
75        let num_island_bodies = islands.active_island(island_id).len();
76        self.bodies_color.clear();
77        self.interaction_indices.clear();
78        self.groups.clear();
79        self.sorted_interactions.clear();
80        self.interaction_colors.clear();
81
82        let mut color_len = [0; 128];
83        self.bodies_color.resize(num_island_bodies, 0u128);
84        self.interaction_indices
85            .extend_from_slice(interaction_indices);
86        self.interaction_colors.resize(interaction_indices.len(), 0);
87        let bcolors = &mut self.bodies_color;
88
89        for (interaction_id, color) in self
90            .interaction_indices
91            .iter()
92            .zip(self.interaction_colors.iter_mut())
93        {
94            let mut body_pair = interactions[*interaction_id].body_pair();
95            let is_fixed1 = body_pair.0.map(|b| bodies[b].is_fixed()).unwrap_or(true);
96            let is_fixed2 = body_pair.1.map(|b| bodies[b].is_fixed()).unwrap_or(true);
97
98            let representative = |handle: RigidBodyHandle| {
99                if let Some(link) = multibodies.rigid_body_link(handle).copied() {
100                    let multibody = multibodies.get_multibody(link.multibody).unwrap();
101                    multibody
102                        .link(1) // Use the link 1 to cover the case where the multibody root is fixed.
103                        .or(multibody.link(0)) // TODO: Never happens?
104                        .map(|l| l.rigid_body)
105                        .unwrap()
106                } else {
107                    handle
108                }
109            };
110
111            body_pair = (
112                body_pair.0.map(representative),
113                body_pair.1.map(representative),
114            );
115
116            match (is_fixed1, is_fixed2) {
117                (false, false) => {
118                    let rb1 = &bodies[body_pair.0.unwrap()];
119                    let rb2 = &bodies[body_pair.1.unwrap()];
120                    let color_mask = bcolors[rb1.ids.active_set_offset as usize]
121                        | bcolors[rb2.ids.active_set_offset as usize];
122                    *color = (!color_mask).trailing_zeros() as usize;
123                    color_len[*color] += 1;
124                    bcolors[rb1.ids.active_set_offset as usize] |= 1 << *color;
125                    bcolors[rb2.ids.active_set_offset as usize] |= 1 << *color;
126                }
127                (true, false) => {
128                    let rb2 = &bodies[body_pair.1.unwrap()];
129                    let color_mask = bcolors[rb2.ids.active_set_offset as usize];
130                    *color = 127 - (!color_mask).leading_zeros() as usize;
131                    color_len[*color] += 1;
132                    bcolors[rb2.ids.active_set_offset as usize] |= 1 << *color;
133                }
134                (false, true) => {
135                    let rb1 = &bodies[body_pair.0.unwrap()];
136                    let color_mask = bcolors[rb1.ids.active_set_offset as usize];
137                    *color = 127 - (!color_mask).leading_zeros() as usize;
138                    color_len[*color] += 1;
139                    bcolors[rb1.ids.active_set_offset as usize] |= 1 << *color;
140                }
141                (true, true) => unreachable!(),
142            }
143        }
144
145        let mut sort_offsets = [0; 128];
146        let mut last_offset = 0;
147
148        for i in 0..128 {
149            if color_len[i] != 0 {
150                self.groups.push(last_offset);
151                sort_offsets[i] = last_offset;
152                last_offset += color_len[i];
153            }
154        }
155
156        self.sorted_interactions
157            .resize(interaction_indices.len(), 0);
158
159        for (interaction_id, color) in interaction_indices
160            .iter()
161            .zip(self.interaction_colors.iter())
162        {
163            self.sorted_interactions[sort_offsets[*color]] = *interaction_id;
164            sort_offsets[*color] += 1;
165        }
166
167        self.groups.push(self.sorted_interactions.len());
168    }
169}
170
171pub(crate) struct InteractionGroups {
172    #[cfg(feature = "simd-is-enabled")]
173    buckets: VecMap<([usize; SIMD_WIDTH], usize)>,
174    #[cfg(feature = "simd-is-enabled")]
175    body_masks: Vec<u128>,
176    pub simd_interactions: Vec<ContactManifoldIndex>,
177    pub nongrouped_interactions: Vec<ContactManifoldIndex>,
178}
179
180impl InteractionGroups {
181    pub fn new() -> Self {
182        Self {
183            #[cfg(feature = "simd-is-enabled")]
184            buckets: VecMap::new(),
185            #[cfg(feature = "simd-is-enabled")]
186            body_masks: Vec::new(),
187            simd_interactions: Vec::new(),
188            nongrouped_interactions: Vec::new(),
189        }
190    }
191
192    // #[cfg(not(feature = "parallel"))]
193    // pub fn clear(&mut self) {
194    //     #[cfg(feature = "simd-is-enabled")]
195    //     {
196    //         self.buckets.clear();
197    //         self.body_masks.clear();
198    //         self.simd_interactions.clear();
199    //     }
200    //     self.nongrouped_interactions.clear();
201    // }
202
203    // TODO: there is a lot of duplicated code with group_manifolds here.
204    // But we don't refactor just now because we may end up with distinct
205    // grouping strategies in the future.
206    #[cfg(not(feature = "simd-is-enabled"))]
207    pub fn group_joints(
208        &mut self,
209        _island_id: usize,
210        _islands: &IslandManager,
211        _bodies: &RigidBodySet,
212        _interactions: &[JointGraphEdge],
213        interaction_indices: &[JointIndex],
214    ) {
215        self.nongrouped_interactions
216            .extend_from_slice(interaction_indices);
217    }
218
219    #[cfg(feature = "simd-is-enabled")]
220    #[profiling::function]
221    pub fn group_joints(
222        &mut self,
223        island_id: usize,
224        islands: &IslandManager,
225        bodies: &RigidBodySet,
226        interactions: &[JointGraphEdge],
227        interaction_indices: &[JointIndex],
228    ) {
229        // TODO: right now, we only sort based on the axes locked by the joint.
230        // We could also take motors and limits into account in the future (most of
231        // the SIMD constraints generation for motors and limits is already implemented).
232        #[cfg(feature = "dim3")]
233        const NUM_JOINT_TYPES: usize = 64;
234        #[cfg(feature = "dim2")]
235        const NUM_JOINT_TYPES: usize = 8;
236
237        // The j-th bit of joint_type_conflicts[i] indicates that the
238        // j-th bucket contains a joint with a type different than `i`.
239        let mut joint_type_conflicts = [0u128; NUM_JOINT_TYPES];
240
241        // Note: each bit of a body mask indicates what bucket already contains
242        // a constraints involving this body.
243        // TODO: currently, this is a bit overconservative because when a bucket
244        // is full, we don't clear the corresponding body mask bit. This may result
245        // in less grouped constraints.
246        self.body_masks
247            .resize(islands.active_island(island_id).len(), 0u128);
248
249        // NOTE: each bit of the occupied mask indicates what bucket already
250        // contains at least one constraint.
251        let mut occupied_mask = 0u128;
252
253        for interaction_i in interaction_indices {
254            let interaction = &interactions[*interaction_i].weight;
255
256            let rb1 = &bodies[interaction.body1];
257            let rb2 = &bodies[interaction.body2];
258
259            let is_fixed1 = !rb1.is_dynamic_or_kinematic();
260            let is_fixed2 = !rb2.is_dynamic_or_kinematic();
261
262            if is_fixed1 && is_fixed2 {
263                continue;
264            }
265
266            if !interaction.data.supports_simd_constraints() {
267                // This joint does not support simd constraints yet.
268                self.nongrouped_interactions.push(*interaction_i);
269                continue;
270            }
271
272            let ijoint = interaction.data.locked_axes.bits() as usize;
273            let i1 = rb1.ids.active_set_offset;
274            let i2 = rb2.ids.active_set_offset;
275            let conflicts = self
276                .body_masks
277                .get(i1 as usize)
278                .copied()
279                .unwrap_or_default()
280                | self
281                    .body_masks
282                    .get(i2 as usize)
283                    .copied()
284                    .unwrap_or_default()
285                | joint_type_conflicts[ijoint];
286            let conflictfree_targets = !(conflicts & occupied_mask); // The & is because we consider empty buckets as free of conflicts.
287            let conflictfree_occupied_targets = conflictfree_targets & occupied_mask;
288
289            let target_index = if conflictfree_occupied_targets != 0 {
290                // Try to fill partial WContacts first.
291                conflictfree_occupied_targets.trailing_zeros()
292            } else {
293                conflictfree_targets.trailing_zeros()
294            };
295
296            if target_index == 128 {
297                // The interaction conflicts with every bucket we can manage.
298                // So push it in a nongrouped interaction list that won't be combined with
299                // any other interactions.
300                self.nongrouped_interactions.push(*interaction_i);
301                continue;
302            }
303
304            let target_mask_bit = 1 << target_index;
305
306            let bucket = self
307                .buckets
308                .entry(target_index as usize)
309                .or_insert_with(|| ([0; SIMD_WIDTH], 0));
310
311            if bucket.1 == SIMD_LAST_INDEX {
312                // We completed our group.
313                (bucket.0)[SIMD_LAST_INDEX] = *interaction_i;
314                self.simd_interactions.extend_from_slice(&bucket.0);
315                bucket.1 = 0;
316                occupied_mask &= !target_mask_bit;
317
318                for k in 0..NUM_JOINT_TYPES {
319                    joint_type_conflicts[k] &= !target_mask_bit;
320                }
321            } else {
322                (bucket.0)[bucket.1] = *interaction_i;
323                bucket.1 += 1;
324                occupied_mask |= target_mask_bit;
325
326                for k in 0..ijoint {
327                    joint_type_conflicts[k] |= target_mask_bit;
328                }
329                for k in ijoint + 1..NUM_JOINT_TYPES {
330                    joint_type_conflicts[k] |= target_mask_bit;
331                }
332            }
333
334            // NOTE: fixed bodies don't transmit forces. Therefore they don't
335            // imply any interaction conflicts.
336            if !is_fixed1 {
337                self.body_masks[i1 as usize] |= target_mask_bit;
338            }
339
340            if !is_fixed2 {
341                self.body_masks[i2 as usize] |= target_mask_bit;
342            }
343        }
344
345        self.nongrouped_interactions.extend(
346            self.buckets
347                .values()
348                .flat_map(|e| e.0.iter().take(e.1).copied()),
349        );
350        self.buckets.clear();
351        self.body_masks.iter_mut().for_each(|e| *e = 0);
352
353        assert!(
354            self.simd_interactions.len() % SIMD_WIDTH == 0,
355            "Invalid SIMD contact grouping."
356        );
357
358        //        println!(
359        //            "Num grouped interactions: {}, nongrouped: {}",
360        //            self.simd_interactions.len(),
361        //            self.nongrouped_interactions.len()
362        //        );
363    }
364
365    pub fn clear_groups(&mut self) {
366        self.simd_interactions.clear();
367        self.nongrouped_interactions.clear();
368    }
369
370    #[cfg(not(feature = "simd-is-enabled"))]
371    pub fn group_manifolds(
372        &mut self,
373        _island_id: usize,
374        _islands: &IslandManager,
375        _bodies: &RigidBodySet,
376        _interactions: &[&mut ContactManifold],
377        interaction_indices: &[ContactManifoldIndex],
378    ) {
379        self.nongrouped_interactions
380            .extend_from_slice(interaction_indices);
381    }
382
383    #[cfg(feature = "simd-is-enabled")]
384    pub fn group_manifolds(
385        &mut self,
386        island_id: usize,
387        islands: &IslandManager,
388        bodies: &RigidBodySet,
389        interactions: &[&mut ContactManifold],
390        interaction_indices: &[ContactManifoldIndex],
391    ) {
392        // Note: each bit of a body mask indicates what bucket already contains
393        // a constraints involving this body.
394        // TODO: currently, this is a bit overconservative because when a bucket
395        // is full, we don't clear the corresponding body mask bit. This may result
396        // in less grouped contacts.
397        // NOTE: body_masks and buckets are already cleared/zeroed at the end of each sort loop.
398        self.body_masks
399            .resize(islands.active_island(island_id).len(), 0u128);
400
401        // NOTE: each bit of the occupied mask indicates what bucket already
402        // contains at least one constraint.
403        let mut occupied_mask = 0u128;
404        let max_interaction_points = interaction_indices
405            .iter()
406            .map(|i| interactions[*i].data.num_active_contacts())
407            .max()
408            .unwrap_or(1);
409
410        // TODO: find a way to reduce the number of iteration.
411        // There must be a way to iterate just once on every interaction indices
412        // instead of MAX_MANIFOLD_POINTS times.
413        for k in 1..=max_interaction_points {
414            for interaction_i in interaction_indices {
415                let interaction = &interactions[*interaction_i];
416
417                // TODO: how could we avoid iterating
418                // on each interaction at every iteration on k?
419                if interaction.data.num_active_contacts() != k {
420                    continue;
421                }
422
423                let (status1, active_set_offset1) = if let Some(rb1) = interaction.data.rigid_body1
424                {
425                    let rb1 = &bodies[rb1];
426                    (rb1.body_type, rb1.ids.active_set_offset)
427                } else {
428                    (RigidBodyType::Fixed, u32::MAX)
429                };
430                let (status2, active_set_offset2) = if let Some(rb2) = interaction.data.rigid_body2
431                {
432                    let rb2 = &bodies[rb2];
433                    (rb2.body_type, rb2.ids.active_set_offset)
434                } else {
435                    (RigidBodyType::Fixed, u32::MAX)
436                };
437
438                let is_fixed1 = !status1.is_dynamic_or_kinematic();
439                let is_fixed2 = !status2.is_dynamic_or_kinematic();
440
441                // TODO: don't generate interactions between fixed bodies in the first place.
442                if is_fixed1 && is_fixed2 {
443                    continue;
444                }
445
446                let i1 = active_set_offset1;
447                let i2 = active_set_offset2;
448                let mask1 = if !is_fixed1 {
449                    self.body_masks[i1 as usize]
450                } else {
451                    0
452                };
453                let mask2 = if !is_fixed2 {
454                    self.body_masks[i2 as usize]
455                } else {
456                    0
457                };
458                let conflicts = mask1 | mask2;
459                let conflictfree_targets = !(conflicts & occupied_mask); // The & is because we consider empty buckets as free of conflicts.
460                let conflictfree_occupied_targets = conflictfree_targets & occupied_mask;
461
462                let target_index = if conflictfree_occupied_targets != 0 {
463                    // Try to fill partial WContacts first.
464                    conflictfree_occupied_targets.trailing_zeros()
465                } else {
466                    conflictfree_targets.trailing_zeros()
467                };
468
469                if target_index == 128 {
470                    // The interaction conflicts with every bucket we can manage.
471                    // So push it in an nongrouped interaction list that won't be combined with
472                    // any other interactions.
473                    self.nongrouped_interactions.push(*interaction_i);
474                    continue;
475                }
476
477                let target_mask_bit = 1 << target_index;
478
479                let bucket = self
480                    .buckets
481                    .entry(target_index as usize)
482                    .or_insert_with(|| ([0; SIMD_WIDTH], 0));
483
484                if bucket.1 == SIMD_LAST_INDEX {
485                    // We completed our group.
486                    (bucket.0)[SIMD_LAST_INDEX] = *interaction_i;
487                    self.simd_interactions.extend_from_slice(&bucket.0);
488                    bucket.1 = 0;
489                    occupied_mask &= !target_mask_bit;
490                } else {
491                    (bucket.0)[bucket.1] = *interaction_i;
492                    bucket.1 += 1;
493                    occupied_mask |= target_mask_bit;
494                }
495
496                // NOTE: fixed bodies don't transmit forces. Therefore they don't
497                // imply any interaction conflicts.
498                if !is_fixed1 {
499                    self.body_masks[i1 as usize] |= target_mask_bit;
500                }
501
502                if !is_fixed2 {
503                    self.body_masks[i2 as usize] |= target_mask_bit;
504                }
505            }
506
507            self.nongrouped_interactions.extend(
508                self.buckets
509                    .values()
510                    .flat_map(|e| e.0.iter().take(e.1).copied()),
511            );
512            self.buckets.clear();
513            self.body_masks.iter_mut().for_each(|e| *e = 0);
514            occupied_mask = 0u128;
515        }
516
517        assert!(
518            self.simd_interactions.len() % SIMD_WIDTH == 0,
519            "Invalid SIMD contact grouping."
520        );
521    }
522}