Skip to main content

box2d_rust/
solver_set.rs

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