Skip to main content

box3d_rust/
solver_set.rs

1// Port of solver_set.h and the wake/sleep/transfer logic from solver_set.c.
2//
3// SPDX-FileCopyrightText: 2025 Erin Catto
4// SPDX-License-Identifier: MIT
5
6use crate::body::{BodySim, BodyState};
7use crate::core::NULL_INDEX;
8use crate::island::IslandSim;
9use crate::joint::JointSim;
10use crate::world::World;
11
12/// Static set for static bodies and joints between static bodies. (b3_staticSet)
13pub const STATIC_SET: i32 = 0;
14/// Disabled set for disabled bodies and their joints. (b3_disabledSet)
15pub const DISABLED_SET: i32 = 1;
16/// Awake set for awake bodies with body states. (b3_awakeSet)
17pub const AWAKE_SET: i32 = 2;
18/// Index of the first sleeping set. (b3_firstSleepingSet)
19pub const FIRST_SLEEPING_SET: i32 = 3;
20
21/// Solver set data for contiguous storage of sims.
22///
23/// Sets used:
24/// - static set for all static bodies and joints between static bodies
25/// - active set for all active bodies with body states (no contacts or joints)
26/// - disabled set for disabled bodies and their joints
27/// - all further sets are sleeping island sets along with their contacts and joints
28///
29/// Purpose: high memory locality.
30/// <https://www.youtube.com/watch?v=nZNd5FjSquk> (b3SolverSet)
31#[derive(Debug, Clone, Default)]
32pub struct SolverSet {
33    /// Body array. Empty for unused set.
34    pub body_sims: Vec<BodySim>,
35
36    /// Body state only exists for active set
37    pub body_states: Vec<BodyState>,
38
39    /// Sleeping/disabled joints. Empty for static/active set.
40    pub joint_sims: Vec<JointSim>,
41
42    /// All contacts for sleeping sets; non-touching contacts for the awake set.
43    /// Empty for static and disabled sets. (C: b3Array(int) contactIndices)
44    pub contact_indices: Vec<i32>,
45
46    /// Awake set has an array of islands. Sleeping sets normally have a single
47    /// island; joints between sleeping sets cause merges with multiple islands.
48    /// Static and disabled sets have no islands.
49    pub island_sims: Vec<IslandSim>,
50
51    /// Aligns with World::solver_set_id_pool.
52    pub set_index: i32,
53}
54
55impl SolverSet {
56    /// Empty set marked free. (slot after destroy)
57    pub fn free_slot() -> SolverSet {
58        SolverSet {
59            set_index: NULL_INDEX,
60            ..Default::default()
61        }
62    }
63}
64
65/// (b3DestroySolverSet)
66pub fn destroy_solver_set(world: &mut World, set_index: i32) {
67    let set = &mut world.solver_sets[set_index as usize];
68    *set = SolverSet::default();
69    set.set_index = NULL_INDEX;
70    world.solver_set_id_pool.free_id(set_index);
71}
72
73/// Wake a solver set. Does not merge islands. (b3WakeSolverSet)
74///
75/// Contacts can be in several places:
76/// 1. non-touching contacts in the disabled set
77/// 2. non-touching contacts already in the awake set
78/// 3. touching contacts in the sleeping set
79///
80/// This handles contact types 1 and 3. Type 2 doesn't need any action.
81pub fn wake_solver_set(world: &mut World, set_index: i32) {
82    use crate::body::IDENTITY_BODY_STATE;
83    use crate::constraint_graph::{add_contact_to_graph, add_joint_to_graph};
84    use crate::contact::contact_flags;
85
86    debug_assert!(set_index >= FIRST_SLEEPING_SET);
87
88    let body_count = world.solver_sets[set_index as usize].body_sims.len();
89    for i in 0..body_count {
90        let sim_src = world.solver_sets[set_index as usize].body_sims[i];
91        let body_id = sim_src.body_id;
92
93        debug_assert!(world.bodies[body_id as usize].set_index == set_index);
94        let awake_body_count = world.solver_sets[AWAKE_SET as usize].body_sims.len() as i32;
95        let flags = {
96            let body = &mut world.bodies[body_id as usize];
97            body.set_index = AWAKE_SET;
98            body.local_index = awake_body_count;
99            // Reset sleep timer
100            body.sleep_time = 0.0;
101            body.flags
102        };
103
104        world.solver_sets[AWAKE_SET as usize]
105            .body_sims
106            .push(sim_src);
107
108        let mut state = IDENTITY_BODY_STATE;
109        state.flags = flags;
110        world.solver_sets[AWAKE_SET as usize]
111            .body_states
112            .push(state);
113
114        // move non-touching contacts from disabled set to awake set
115        let mut contact_key = world.bodies[body_id as usize].head_contact_key;
116        while contact_key != NULL_INDEX {
117            let edge_index = (contact_key & 1) as usize;
118            let contact_id = contact_key >> 1;
119
120            contact_key = world.contacts[contact_id as usize].edges[edge_index].next_key;
121
122            if world.contacts[contact_id as usize].set_index != DISABLED_SET {
123                debug_assert!(
124                    world.contacts[contact_id as usize].set_index == AWAKE_SET
125                        || world.contacts[contact_id as usize].set_index == set_index
126                );
127                continue;
128            }
129
130            let local_index = world.contacts[contact_id as usize].local_index;
131
132            debug_assert!(
133                0 <= local_index
134                    && (local_index as usize)
135                        < world.solver_sets[DISABLED_SET as usize]
136                            .contact_indices
137                            .len()
138            );
139            debug_assert!(
140                world.solver_sets[DISABLED_SET as usize].contact_indices[local_index as usize]
141                    == contact_id
142            );
143
144            debug_assert!(
145                (world.contacts[contact_id as usize].flags & contact_flags::TOUCHING) == 0
146                    && world.contacts[contact_id as usize].manifold_count() == 0
147            );
148
149            {
150                let awake_contact_count =
151                    world.solver_sets[AWAKE_SET as usize].contact_indices.len() as i32;
152                let contact = &mut world.contacts[contact_id as usize];
153                contact.set_index = AWAKE_SET;
154                contact.local_index = awake_contact_count;
155            }
156            world.solver_sets[AWAKE_SET as usize]
157                .contact_indices
158                .push(contact_id);
159
160            let disabled_contacts = &mut world.solver_sets[DISABLED_SET as usize].contact_indices;
161            let moved_local_index = disabled_contacts.len() as i32 - 1;
162            disabled_contacts.swap_remove(local_index as usize);
163            if moved_local_index != local_index {
164                // fix moved element
165                let moved_contact_index = disabled_contacts[local_index as usize];
166                let moved_contact = &mut world.contacts[moved_contact_index as usize];
167                debug_assert!(moved_contact.local_index == moved_local_index);
168                moved_contact.local_index = local_index;
169            }
170        }
171    }
172
173    // Transfer touching contacts from sleeping set to constraint graph.
174    {
175        let contact_count = world.solver_sets[set_index as usize].contact_indices.len();
176        for i in 0..contact_count {
177            let contact_id = world.solver_sets[set_index as usize].contact_indices[i];
178            debug_assert!(
179                (world.contacts[contact_id as usize].flags & contact_flags::TOUCHING) != 0
180            );
181            debug_assert!(
182                (world.contacts[contact_id as usize].flags & contact_flags::SIM_TOUCHING) != 0
183            );
184            debug_assert!(world.contacts[contact_id as usize].set_index == set_index);
185            add_contact_to_graph(world, contact_id);
186            world.contacts[contact_id as usize].set_index = AWAKE_SET;
187        }
188    }
189
190    // transfer joints from sleeping set to awake set
191    {
192        let joint_count = world.solver_sets[set_index as usize].joint_sims.len();
193        for i in 0..joint_count {
194            let joint_sim = world.solver_sets[set_index as usize].joint_sims[i];
195            let joint_id = joint_sim.joint_id;
196            debug_assert!(world.joints[joint_id as usize].set_index == set_index);
197            add_joint_to_graph(world, joint_sim, joint_id);
198            world.joints[joint_id as usize].set_index = AWAKE_SET;
199        }
200    }
201
202    // transfer island from sleeping set to awake set
203    // Usually a sleeping set has only one island, but it is possible
204    // that joints are created between sleeping islands and they
205    // are moved to the same sleeping set.
206    let island_count = world.solver_sets[set_index as usize].island_sims.len();
207    for i in 0..island_count {
208        let island_src = world.solver_sets[set_index as usize].island_sims[i];
209        let island_id = island_src.island_id;
210        let awake_island_count = world.solver_sets[AWAKE_SET as usize].island_sims.len() as i32;
211        {
212            let island = &mut world.islands[island_id as usize];
213            island.set_index = AWAKE_SET;
214            island.local_index = awake_island_count;
215        }
216        world.solver_sets[AWAKE_SET as usize]
217            .island_sims
218            .push(island_src);
219    }
220
221    // destroy the sleeping set
222    destroy_solver_set(world, set_index);
223}
224
225/// Move a sleeping island (bodies, contacts, joints) from the awake set into a
226/// new sleeping solver set. (b3TrySleepIsland)
227pub fn try_sleep_island(world: &mut World, island_id: i32) {
228    use crate::constants::GRAPH_COLOR_COUNT;
229    use crate::constraint_graph::OVERFLOW_INDEX;
230    use crate::contact::contact_flags;
231
232    debug_assert!(world.islands[island_id as usize].set_index == AWAKE_SET);
233
234    // Cannot put an island to sleep while it has a pending split and more than one body.
235    if world.islands[island_id as usize].constraint_remove_count > 0
236        && world.islands[island_id as usize].bodies.len() > 1
237    {
238        return;
239    }
240
241    // island is sleeping
242    // - create new sleeping solver set
243    // - move island to sleeping solver set
244    // - identify non-touching contacts that should move to sleeping solver set or disabled set
245    // - remove old island
246    // - fix island
247    let sleep_set_id = world.solver_set_id_pool.alloc_id();
248    if sleep_set_id == world.solver_sets.len() as i32 {
249        world.solver_sets.push(SolverSet::free_slot());
250    }
251
252    {
253        let island = &world.islands[island_id as usize];
254        debug_assert!(
255            0 <= island.local_index
256                && (island.local_index as usize)
257                    < world.solver_sets[AWAKE_SET as usize].island_sims.len()
258        );
259
260        let body_capacity = island.bodies.len();
261        let contact_capacity = island.contacts.len();
262        let joint_capacity = island.joints.len();
263
264        let sleep_set = &mut world.solver_sets[sleep_set_id as usize];
265        *sleep_set = SolverSet::default();
266        sleep_set.set_index = sleep_set_id;
267        sleep_set.body_sims.reserve(body_capacity);
268        sleep_set.contact_indices.reserve(contact_capacity);
269        sleep_set.joint_sims.reserve(joint_capacity);
270    }
271
272    // move awake bodies to sleeping set
273    // this shuffles around bodies in the awake set
274    {
275        let body_count = world.islands[island_id as usize].bodies.len();
276        for i in 0..body_count {
277            let body_id = world.islands[island_id as usize].bodies[i];
278            debug_assert!(world.bodies[body_id as usize].set_index == AWAKE_SET);
279            debug_assert!(world.bodies[body_id as usize].island_id == island_id);
280            debug_assert!(world.bodies[body_id as usize].island_index == i as i32);
281
282            // Update the body move event to indicate this body fell asleep
283            // It could happen the body is forced asleep before it ever moves.
284            let body_move_index = world.bodies[body_id as usize].body_move_index;
285            if body_move_index != NULL_INDEX {
286                let move_event = &mut world.body_move_events[body_move_index as usize];
287                debug_assert!(move_event.body_id.index1 - 1 == body_id);
288                debug_assert!(
289                    move_event.body_id.generation == world.bodies[body_id as usize].generation
290                );
291                move_event.fell_asleep = true;
292                world.bodies[body_id as usize].body_move_index = NULL_INDEX;
293            }
294
295            let awake_body_index = world.bodies[body_id as usize].local_index;
296
297            // move body sim to sleep set
298            let awake_sim =
299                world.solver_sets[AWAKE_SET as usize].body_sims[awake_body_index as usize];
300            let sleep_body_index = world.solver_sets[sleep_set_id as usize].body_sims.len() as i32;
301            world.solver_sets[sleep_set_id as usize]
302                .body_sims
303                .push(awake_sim);
304
305            {
306                let awake_sims = &mut world.solver_sets[AWAKE_SET as usize].body_sims;
307                let moved_index = awake_sims.len() as i32 - 1;
308                awake_sims.swap_remove(awake_body_index as usize);
309                if moved_index != awake_body_index {
310                    // fix local index on moved element
311                    let moved_id = awake_sims[awake_body_index as usize].body_id;
312                    let moved_body = &mut world.bodies[moved_id as usize];
313                    debug_assert!(moved_body.local_index == moved_index);
314                    moved_body.local_index = awake_body_index;
315                }
316            }
317
318            // destroy state, no need to clone
319            world.solver_sets[AWAKE_SET as usize]
320                .body_states
321                .swap_remove(awake_body_index as usize);
322
323            {
324                let body = &mut world.bodies[body_id as usize];
325                body.set_index = sleep_set_id;
326                body.local_index = sleep_body_index;
327            }
328
329            // Move non-touching contacts to the disabled set.
330            // Non-touching contacts may exist between sleeping islands and there is no clear ownership.
331            let mut contact_key = world.bodies[body_id as usize].head_contact_key;
332            while contact_key != NULL_INDEX {
333                let contact_id = contact_key >> 1;
334                let edge_index = (contact_key & 1) as usize;
335
336                debug_assert!(
337                    world.contacts[contact_id as usize].set_index == AWAKE_SET
338                        || world.contacts[contact_id as usize].set_index == DISABLED_SET
339                );
340                contact_key = world.contacts[contact_id as usize].edges[edge_index].next_key;
341
342                if world.contacts[contact_id as usize].set_index == DISABLED_SET {
343                    // already moved to disabled set by another body in the island
344                    continue;
345                }
346
347                if world.contacts[contact_id as usize].color_index != NULL_INDEX {
348                    // contact is touching and will be moved separately
349                    debug_assert!(
350                        (world.contacts[contact_id as usize].flags & contact_flags::TOUCHING) != 0
351                    );
352                    continue;
353                }
354
355                // the other body may still be awake, it still may go to sleep and then it will be responsible
356                // for moving this contact to the disabled set.
357                let other_edge_index = edge_index ^ 1;
358                let other_body_id =
359                    world.contacts[contact_id as usize].edges[other_edge_index].body_id;
360                if world.bodies[other_body_id as usize].set_index == AWAKE_SET {
361                    continue;
362                }
363
364                let local_index = world.contacts[contact_id as usize].local_index;
365                debug_assert!(
366                    world.solver_sets[AWAKE_SET as usize].contact_indices[local_index as usize]
367                        == contact_id
368                );
369
370                debug_assert!(world.contacts[contact_id as usize].manifold_count() == 0);
371                debug_assert!(
372                    (world.contacts[contact_id as usize].flags & contact_flags::TOUCHING) == 0
373                );
374
375                // Move the non-touching contact to the disabled set.
376                {
377                    let disabled_contact_count = world.solver_sets[DISABLED_SET as usize]
378                        .contact_indices
379                        .len() as i32;
380                    let contact = &mut world.contacts[contact_id as usize];
381                    contact.set_index = DISABLED_SET;
382
383                    // This is mandatory for validation to work correctly
384                    contact.local_index = disabled_contact_count;
385                }
386                world.solver_sets[DISABLED_SET as usize]
387                    .contact_indices
388                    .push(contact_id);
389
390                let awake_contacts = &mut world.solver_sets[AWAKE_SET as usize].contact_indices;
391                let moved_local_index = awake_contacts.len() as i32 - 1;
392                awake_contacts.swap_remove(local_index as usize);
393                if moved_local_index != local_index {
394                    // fix moved element
395                    let moved_contact_index = awake_contacts[local_index as usize];
396                    let moved_contact = &mut world.contacts[moved_contact_index as usize];
397                    debug_assert!(moved_contact.local_index == moved_local_index);
398                    moved_contact.local_index = local_index;
399                }
400            }
401        }
402    }
403
404    // move touching contacts to sleeping set
405    // this shuffles contacts in the awake set
406    {
407        let contact_count = world.islands[island_id as usize].contacts.len();
408        for i in 0..contact_count {
409            let contact_id = world.islands[island_id as usize].contacts[i].contact_id;
410            debug_assert!(world.contacts[contact_id as usize].set_index == AWAKE_SET);
411            debug_assert!(world.contacts[contact_id as usize].island_id == island_id);
412            debug_assert!(world.contacts[contact_id as usize].island_index == i as i32);
413            let color_index = world.contacts[contact_id as usize].color_index;
414            debug_assert!(0 <= color_index && color_index < GRAPH_COLOR_COUNT);
415
416            // Remove bodies from graph coloring associated with this constraint
417            if color_index != OVERFLOW_INDEX {
418                // might clear a bit for a static body, but this has no effect
419                let body_id_a = world.contacts[contact_id as usize].edges[0].body_id;
420                let body_id_b = world.contacts[contact_id as usize].edges[1].body_id;
421                let color = &mut world.constraint_graph.colors[color_index as usize];
422                color.body_set.clear_bit(body_id_a as u32);
423                color.body_set.clear_bit(body_id_b as u32);
424            }
425
426            let sleep_contact_index = world.solver_sets[sleep_set_id as usize]
427                .contact_indices
428                .len() as i32;
429            world.solver_sets[sleep_set_id as usize]
430                .contact_indices
431                .push(contact_id);
432
433            let local_index = world.contacts[contact_id as usize].local_index;
434            let is_mesh =
435                (world.contacts[contact_id as usize].flags & contact_flags::SIM_MESH_CONTACT) != 0;
436            if is_mesh || color_index == OVERFLOW_INDEX {
437                let color = &mut world.constraint_graph.colors[color_index as usize];
438                let moved_local_index = color.contacts.len() as i32 - 1;
439                color.contacts.swap_remove(local_index as usize);
440                if moved_local_index != local_index {
441                    // fix moved element
442                    let moved_contact_id = color.contacts[local_index as usize].contact_id;
443                    let moved_contact = &mut world.contacts[moved_contact_id as usize];
444                    debug_assert!(moved_contact.local_index == moved_local_index);
445                    moved_contact.local_index = local_index;
446                }
447            } else {
448                let color = &mut world.constraint_graph.colors[color_index as usize];
449                let moved_local_index = color.convex_contacts.len() as i32 - 1;
450                color.convex_contacts.swap_remove(local_index as usize);
451                if moved_local_index != local_index {
452                    // fix moved element
453                    let moved_contact_id = color.convex_contacts[local_index as usize];
454                    let moved_contact = &mut world.contacts[moved_contact_id as usize];
455                    debug_assert!(moved_contact.local_index == moved_local_index);
456                    moved_contact.local_index = local_index;
457                }
458            }
459
460            let contact = &mut world.contacts[contact_id as usize];
461            contact.set_index = sleep_set_id;
462            contact.color_index = NULL_INDEX;
463            contact.local_index = sleep_contact_index;
464        }
465    }
466
467    // move joints
468    // this shuffles joints in the awake set
469    {
470        let joint_count = world.islands[island_id as usize].joints.len();
471        for i in 0..joint_count {
472            let joint_id = world.islands[island_id as usize].joints[i].joint_id;
473            debug_assert!(world.joints[joint_id as usize].set_index == AWAKE_SET);
474            debug_assert!(world.joints[joint_id as usize].island_id == island_id);
475            debug_assert!(world.joints[joint_id as usize].island_index == i as i32);
476            let color_index = world.joints[joint_id as usize].color_index;
477            let local_index = world.joints[joint_id as usize].local_index;
478
479            debug_assert!(0 <= color_index && color_index < GRAPH_COLOR_COUNT);
480
481            let awake_joint_sim = world.constraint_graph.colors[color_index as usize].joint_sims
482                [local_index as usize];
483
484            if color_index != OVERFLOW_INDEX {
485                // might clear a bit for a static body, but this has no effect
486                let body_id_a = world.joints[joint_id as usize].edges[0].body_id;
487                let body_id_b = world.joints[joint_id as usize].edges[1].body_id;
488                let color = &mut world.constraint_graph.colors[color_index as usize];
489                color.body_set.clear_bit(body_id_a as u32);
490                color.body_set.clear_bit(body_id_b as u32);
491            }
492
493            let sleep_joint_index =
494                world.solver_sets[sleep_set_id as usize].joint_sims.len() as i32;
495            world.solver_sets[sleep_set_id as usize]
496                .joint_sims
497                .push(awake_joint_sim);
498
499            {
500                let color = &mut world.constraint_graph.colors[color_index as usize];
501                let moved_index = color.joint_sims.len() as i32 - 1;
502                color.joint_sims.swap_remove(local_index as usize);
503                if moved_index != local_index {
504                    // fix moved element
505                    let moved_id = color.joint_sims[local_index as usize].joint_id;
506                    let moved_joint = &mut world.joints[moved_id as usize];
507                    debug_assert!(moved_joint.local_index == moved_index);
508                    moved_joint.local_index = local_index;
509                }
510            }
511
512            let joint = &mut world.joints[joint_id as usize];
513            joint.set_index = sleep_set_id;
514            joint.color_index = NULL_INDEX;
515            joint.local_index = sleep_joint_index;
516        }
517    }
518
519    // move island struct
520    {
521        debug_assert!(world.islands[island_id as usize].set_index == AWAKE_SET);
522
523        let island_index = world.islands[island_id as usize].local_index;
524        world.solver_sets[sleep_set_id as usize]
525            .island_sims
526            .push(IslandSim { island_id });
527
528        let awake_islands = &mut world.solver_sets[AWAKE_SET as usize].island_sims;
529        let moved_island_index = awake_islands.len() as i32 - 1;
530        awake_islands.swap_remove(island_index as usize);
531        if moved_island_index != island_index {
532            // fix index on moved element
533            let moved_island_id = awake_islands[island_index as usize].island_id;
534            let moved_island = &mut world.islands[moved_island_id as usize];
535            debug_assert!(moved_island.local_index == moved_island_index);
536            moved_island.local_index = island_index;
537        }
538
539        let island = &mut world.islands[island_id as usize];
540        island.set_index = sleep_set_id;
541        island.local_index = 0;
542    }
543
544    if world.split_island_id == island_id {
545        world.split_island_id = NULL_INDEX;
546    }
547
548    world.validate_solver_sets();
549}
550
551/// This is called when joints are created between sets. I want to allow the sets
552/// to continue sleeping if both are asleep. Otherwise one set is waked.
553/// Islands will get merged when the set is woke. (b3MergeSolverSets)
554pub fn merge_solver_sets(world: &mut World, set_id1: i32, set_id2: i32) {
555    debug_assert!(set_id1 >= FIRST_SLEEPING_SET);
556    debug_assert!(set_id2 >= FIRST_SLEEPING_SET);
557
558    // Move the fewest number of bodies
559    let count1 = world.solver_sets[set_id1 as usize].body_sims.len();
560    let count2 = world.solver_sets[set_id2 as usize].body_sims.len();
561    let (set_id1, set_id2) = if count1 < count2 {
562        (set_id2, set_id1)
563    } else {
564        (set_id1, set_id2)
565    };
566
567    // transfer bodies
568    {
569        let body_count = world.solver_sets[set_id2 as usize].body_sims.len();
570        for i in 0..body_count {
571            let sim_src = world.solver_sets[set_id2 as usize].body_sims[i];
572
573            let target_count = world.solver_sets[set_id1 as usize].body_sims.len() as i32;
574            let body = &mut world.bodies[sim_src.body_id as usize];
575            debug_assert!(body.set_index == set_id2);
576            body.set_index = set_id1;
577            body.local_index = target_count;
578
579            world.solver_sets[set_id1 as usize].body_sims.push(sim_src);
580        }
581    }
582
583    // transfer contacts
584    {
585        let contact_count = world.solver_sets[set_id2 as usize].contact_indices.len();
586        for i in 0..contact_count {
587            let contact_index = world.solver_sets[set_id2 as usize].contact_indices[i];
588
589            let target_count = world.solver_sets[set_id1 as usize].contact_indices.len() as i32;
590            let contact = &mut world.contacts[contact_index as usize];
591            debug_assert!(contact.set_index == set_id2);
592            contact.set_index = set_id1;
593            contact.local_index = target_count;
594
595            world.solver_sets[set_id1 as usize]
596                .contact_indices
597                .push(contact_index);
598        }
599    }
600
601    // transfer joints
602    {
603        let joint_count = world.solver_sets[set_id2 as usize].joint_sims.len();
604        for i in 0..joint_count {
605            let joint_src = world.solver_sets[set_id2 as usize].joint_sims[i];
606
607            let target_count = world.solver_sets[set_id1 as usize].joint_sims.len() as i32;
608            let joint = &mut world.joints[joint_src.joint_id as usize];
609            debug_assert!(joint.set_index == set_id2);
610            joint.set_index = set_id1;
611            joint.local_index = target_count;
612
613            world.solver_sets[set_id1 as usize]
614                .joint_sims
615                .push(joint_src);
616        }
617    }
618
619    // transfer islands
620    {
621        let island_count = world.solver_sets[set_id2 as usize].island_sims.len();
622        for i in 0..island_count {
623            let island_src = world.solver_sets[set_id2 as usize].island_sims[i];
624            let island_id = island_src.island_id;
625
626            let target_count = world.solver_sets[set_id1 as usize].island_sims.len() as i32;
627            let island = &mut world.islands[island_id as usize];
628            island.set_index = set_id1;
629            island.local_index = target_count;
630
631            world.solver_sets[set_id1 as usize]
632                .island_sims
633                .push(island_src);
634        }
635    }
636
637    // destroy the merged set
638    destroy_solver_set(world, set_id2);
639
640    world.validate_solver_sets();
641}
642
643/// Move a body sim between solver sets. (b3TransferBody)
644pub fn transfer_body(
645    world: &mut World,
646    target_set_index: i32,
647    source_set_index: i32,
648    body_id: i32,
649) {
650    use crate::body::{body_flags, IDENTITY_BODY_STATE};
651
652    if target_set_index == source_set_index {
653        return;
654    }
655
656    let source_index = world.bodies[body_id as usize].local_index;
657    let mut source_sim =
658        world.solver_sets[source_set_index as usize].body_sims[source_index as usize];
659
660    // Clear transient body flags
661    source_sim.flags &=
662        !(body_flags::IS_FAST | body_flags::IS_SPEED_CAPPED | body_flags::HAD_TIME_OF_IMPACT);
663
664    let target_index = world.solver_sets[target_set_index as usize].body_sims.len() as i32;
665    world.solver_sets[target_set_index as usize]
666        .body_sims
667        .push(source_sim);
668
669    // Remove body sim from solver set that owns it
670    {
671        let source_sims = &mut world.solver_sets[source_set_index as usize].body_sims;
672        let moved_index = source_sims.len() as i32 - 1;
673        source_sims.swap_remove(source_index as usize);
674        if moved_index != source_index {
675            // Fix moved body index
676            let moved_id = source_sims[source_index as usize].body_id;
677            let moved_body = &mut world.bodies[moved_id as usize];
678            debug_assert!(moved_body.local_index == moved_index);
679            moved_body.local_index = source_index;
680        }
681    }
682
683    if source_set_index == AWAKE_SET {
684        world.solver_sets[source_set_index as usize]
685            .body_states
686            .swap_remove(source_index as usize);
687    } else if target_set_index == AWAKE_SET {
688        let mut state = IDENTITY_BODY_STATE;
689        state.flags = world.bodies[body_id as usize].flags;
690        world.solver_sets[target_set_index as usize]
691            .body_states
692            .push(state);
693    }
694
695    let body = &mut world.bodies[body_id as usize];
696    body.set_index = target_set_index;
697    body.local_index = target_index;
698}
699
700/// Move a joint sim between solver sets or the constraint graph. (b3TransferJoint)
701pub fn transfer_joint(
702    world: &mut World,
703    target_set_index: i32,
704    source_set_index: i32,
705    joint_id: i32,
706) {
707    use crate::constants::GRAPH_COLOR_COUNT;
708    use crate::constraint_graph::{add_joint_to_graph, remove_joint_from_graph};
709
710    if target_set_index == source_set_index {
711        return;
712    }
713
714    let local_index = world.joints[joint_id as usize].local_index;
715    let color_index = world.joints[joint_id as usize].color_index;
716
717    // Retrieve source.
718    let source_sim = if source_set_index == AWAKE_SET {
719        debug_assert!(0 <= color_index && color_index < GRAPH_COLOR_COUNT);
720        world.constraint_graph.colors[color_index as usize].joint_sims[local_index as usize]
721    } else {
722        debug_assert!(color_index == NULL_INDEX);
723        world.solver_sets[source_set_index as usize].joint_sims[local_index as usize]
724    };
725
726    // Create target and copy. Fix joint.
727    if target_set_index == AWAKE_SET {
728        add_joint_to_graph(world, source_sim, joint_id);
729        world.joints[joint_id as usize].set_index = AWAKE_SET;
730    } else {
731        let target_count = world.solver_sets[target_set_index as usize]
732            .joint_sims
733            .len() as i32;
734        {
735            let joint = &mut world.joints[joint_id as usize];
736            joint.set_index = target_set_index;
737            joint.local_index = target_count;
738            joint.color_index = NULL_INDEX;
739        }
740        world.solver_sets[target_set_index as usize]
741            .joint_sims
742            .push(source_sim);
743    }
744
745    // Destroy source.
746    if source_set_index == AWAKE_SET {
747        let body_id_a = world.joints[joint_id as usize].edges[0].body_id;
748        let body_id_b = world.joints[joint_id as usize].edges[1].body_id;
749        remove_joint_from_graph(world, body_id_a, body_id_b, color_index, local_index);
750    } else {
751        let source_sims = &mut world.solver_sets[source_set_index as usize].joint_sims;
752        let moved_index = source_sims.len() as i32 - 1;
753        source_sims.swap_remove(local_index as usize);
754        if moved_index != local_index {
755            // fix swapped element
756            let moved_id = source_sims[local_index as usize].joint_id;
757            world.joints[moved_id as usize].local_index = local_index;
758        }
759    }
760}