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).
218#[allow(dead_code)]
219pub fn try_sleep_island(world: &mut World, island_id: i32) {
220    debug_assert!(world.islands[island_id as usize].set_index == AWAKE_SET);
221
222    // Cannot put an island to sleep while it has a pending split and more than
223    // one body.
224    if world.islands[island_id as usize].constraint_remove_count > 0
225        && world.islands[island_id as usize].bodies.len() > 1
226    {
227        return;
228    }
229
230    // island is sleeping
231    // - create new sleeping solver set
232    // - move island to sleeping solver set
233    // - identify non-touching contacts that should move to sleeping solver set
234    //   or disabled set
235    // - remove old island
236    // - fix island
237    let sleep_set_id = world.solver_set_id_pool.alloc_id();
238    if sleep_set_id == world.solver_sets.len() as i32 {
239        let set = SolverSet {
240            set_index: NULL_INDEX,
241            ..SolverSet::default()
242        };
243        world.solver_sets.push(set);
244    }
245
246    {
247        let (body_cap, contact_cap, joint_cap) = {
248            let island = &world.islands[island_id as usize];
249            (
250                island.bodies.len(),
251                island.contacts.len(),
252                island.joints.len(),
253            )
254        };
255        let sleep_set = &mut world.solver_sets[sleep_set_id as usize];
256        *sleep_set = SolverSet::default();
257        sleep_set.set_index = sleep_set_id;
258        sleep_set.body_sims.reserve(body_cap);
259        sleep_set.contact_sims.reserve(contact_cap);
260        sleep_set.joint_sims.reserve(joint_cap);
261    }
262
263    debug_assert!({
264        let local = world.islands[island_id as usize].local_index;
265        0 <= local && local < world.solver_sets[AWAKE_SET as usize].island_sims.len() as i32
266    });
267
268    // move awake bodies to sleeping set
269    // this shuffles around bodies in the awake set
270    {
271        let island_body_count = world.islands[island_id as usize].bodies.len();
272        for i in 0..island_body_count {
273            let body_id = world.islands[island_id as usize].bodies[i];
274            debug_assert!(world.bodies[body_id as usize].set_index == AWAKE_SET);
275            debug_assert!(world.bodies[body_id as usize].island_id == island_id);
276            debug_assert!(world.bodies[body_id as usize].island_index == i as i32);
277
278            // Update the body move event to indicate this body fell asleep.
279            // It could happen the body is forced asleep before it ever moves.
280            let body_move_index = world.bodies[body_id as usize].body_move_index;
281            if body_move_index != NULL_INDEX {
282                let move_event = &mut world.body_move_events[body_move_index as usize];
283                debug_assert!(move_event.body_id.index1 - 1 == body_id);
284                debug_assert!(
285                    move_event.body_id.generation == world.bodies[body_id as usize].generation
286                );
287                move_event.fell_asleep = true;
288                world.bodies[body_id as usize].body_move_index = NULL_INDEX;
289            }
290
291            let awake_body_index = world.bodies[body_id as usize].local_index;
292            let awake_sim =
293                world.solver_sets[AWAKE_SET as usize].body_sims[awake_body_index as usize];
294
295            // move body sim to sleep set
296            let sleep_body_index = world.solver_sets[sleep_set_id as usize].body_sims.len() as i32;
297            world.solver_sets[sleep_set_id as usize]
298                .body_sims
299                .push(awake_sim);
300
301            remove_body_sim(
302                &mut world.solver_sets[AWAKE_SET as usize].body_sims,
303                &mut world.bodies,
304                awake_body_index,
305            );
306
307            // destroy state, no need to clone
308            world.solver_sets[AWAKE_SET as usize]
309                .body_states
310                .swap_remove(awake_body_index as usize);
311
312            {
313                let body = &mut world.bodies[body_id as usize];
314                body.set_index = sleep_set_id;
315                body.local_index = sleep_body_index;
316            }
317
318            // Move non-touching contacts to the disabled set.
319            // Non-touching contacts may exist between sleeping islands and
320            // there is no clear ownership.
321            let mut contact_key = world.bodies[body_id as usize].head_contact_key;
322            while contact_key != NULL_INDEX {
323                let contact_id = contact_key >> 1;
324                let edge_index = contact_key & 1;
325
326                debug_assert!(
327                    world.contacts[contact_id as usize].set_index == AWAKE_SET
328                        || world.contacts[contact_id as usize].set_index == DISABLED_SET
329                );
330                contact_key =
331                    world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
332
333                if world.contacts[contact_id as usize].set_index == DISABLED_SET {
334                    // already moved to disabled set by another body in the island
335                    continue;
336                }
337
338                if world.contacts[contact_id as usize].color_index != NULL_INDEX {
339                    // contact is touching and will be moved separately
340                    debug_assert!(
341                        world.contacts[contact_id as usize].flags & contact_flags::TOUCHING != 0
342                    );
343                    continue;
344                }
345
346                // the other body may still be awake, it still may go to sleep
347                // and then it will be responsible for moving this contact to
348                // the disabled set.
349                let other_edge_index = edge_index ^ 1;
350                let other_body_id =
351                    world.contacts[contact_id as usize].edges[other_edge_index as usize].body_id;
352                if world.bodies[other_body_id as usize].set_index == AWAKE_SET {
353                    continue;
354                }
355
356                let local_index = world.contacts[contact_id as usize].local_index;
357                let contact_sim =
358                    world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize];
359
360                debug_assert!(contact_sim.manifold.point_count == 0);
361                debug_assert!(
362                    world.contacts[contact_id as usize].flags & contact_flags::TOUCHING == 0
363                );
364
365                // move the non-touching contact to the disabled set
366                let disabled_count =
367                    world.solver_sets[DISABLED_SET as usize].contact_sims.len() as i32;
368                {
369                    let contact = &mut world.contacts[contact_id as usize];
370                    contact.set_index = DISABLED_SET;
371                    contact.local_index = disabled_count;
372                }
373                world.solver_sets[DISABLED_SET as usize]
374                    .contact_sims
375                    .push(contact_sim);
376
377                let moved_index =
378                    world.solver_sets[AWAKE_SET as usize].contact_sims.len() as i32 - 1;
379                world.solver_sets[AWAKE_SET as usize]
380                    .contact_sims
381                    .swap_remove(local_index as usize);
382                if moved_index != local_index {
383                    // fix moved element
384                    let moved_id = world.solver_sets[AWAKE_SET as usize].contact_sims
385                        [local_index as usize]
386                        .contact_id;
387                    let moved_contact = &mut world.contacts[moved_id as usize];
388                    debug_assert!(moved_contact.local_index == moved_index);
389                    moved_contact.local_index = local_index;
390                }
391            }
392        }
393    }
394
395    // move touching contacts
396    // this shuffles contacts in the awake set
397    {
398        let island_contact_count = world.islands[island_id as usize].contacts.len();
399        for i in 0..island_contact_count {
400            let contact_id = world.islands[island_id as usize].contacts[i].contact_id;
401            let (color_index, local_index, body_id_a, body_id_b) = {
402                let contact = &world.contacts[contact_id as usize];
403                debug_assert!(contact.set_index == AWAKE_SET);
404                debug_assert!(contact.island_id == island_id);
405                (
406                    contact.color_index,
407                    contact.local_index,
408                    contact.edges[0].body_id,
409                    contact.edges[1].body_id,
410                )
411            };
412            debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
413
414            // Remove bodies from graph coloring associated with this constraint
415            if color_index != OVERFLOW_INDEX {
416                // might clear a bit for a static body, but this has no effect
417                let color = &mut world.constraint_graph.colors[color_index as usize];
418                color.body_set.clear_bit(body_id_a as u32);
419                color.body_set.clear_bit(body_id_b as u32);
420            }
421
422            let awake_contact_sim = world.constraint_graph.colors[color_index as usize]
423                .contact_sims[local_index as usize];
424
425            let sleep_contact_index =
426                world.solver_sets[sleep_set_id as usize].contact_sims.len() as i32;
427            world.solver_sets[sleep_set_id as usize]
428                .contact_sims
429                .push(awake_contact_sim);
430
431            let moved_index = world.constraint_graph.colors[color_index as usize]
432                .contact_sims
433                .len() as i32
434                - 1;
435            world.constraint_graph.colors[color_index as usize]
436                .contact_sims
437                .swap_remove(local_index as usize);
438            if moved_index != local_index {
439                // fix moved element
440                let moved_id = world.constraint_graph.colors[color_index as usize].contact_sims
441                    [local_index as usize]
442                    .contact_id;
443                let moved_contact = &mut world.contacts[moved_id as usize];
444                debug_assert!(moved_contact.local_index == moved_index);
445                moved_contact.local_index = local_index;
446            }
447
448            let contact = &mut world.contacts[contact_id as usize];
449            contact.set_index = sleep_set_id;
450            contact.color_index = NULL_INDEX;
451            contact.local_index = sleep_contact_index;
452        }
453    }
454
455    // move joints
456    // this shuffles joints in the awake set
457    {
458        let island_joint_count = world.islands[island_id as usize].joints.len();
459        for i in 0..island_joint_count {
460            let joint_id = world.islands[island_id as usize].joints[i].joint_id;
461            let (color_index, local_index, body_id_a, body_id_b) = {
462                let joint = &world.joints[joint_id as usize];
463                debug_assert!(joint.set_index == AWAKE_SET);
464                debug_assert!(joint.island_id == island_id);
465                (
466                    joint.color_index,
467                    joint.local_index,
468                    joint.edges[0].body_id,
469                    joint.edges[1].body_id,
470                )
471            };
472            debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
473
474            if color_index != OVERFLOW_INDEX {
475                // might clear a bit for a static body, but this has no effect
476                let color = &mut world.constraint_graph.colors[color_index as usize];
477                color.body_set.clear_bit(body_id_a as u32);
478                color.body_set.clear_bit(body_id_b as u32);
479            }
480
481            let awake_joint_sim = world.constraint_graph.colors[color_index as usize].joint_sims
482                [local_index as usize];
483
484            let sleep_joint_index =
485                world.solver_sets[sleep_set_id as usize].joint_sims.len() as i32;
486            world.solver_sets[sleep_set_id as usize]
487                .joint_sims
488                .push(awake_joint_sim);
489
490            let moved_index = world.constraint_graph.colors[color_index as usize]
491                .joint_sims
492                .len() as i32
493                - 1;
494            world.constraint_graph.colors[color_index as usize]
495                .joint_sims
496                .swap_remove(local_index as usize);
497            if moved_index != local_index {
498                // fix moved element
499                let moved_id = world.constraint_graph.colors[color_index as usize].joint_sims
500                    [local_index as usize]
501                    .joint_id;
502                let moved_joint = &mut world.joints[moved_id as usize];
503                debug_assert!(moved_joint.local_index == moved_index);
504                moved_joint.local_index = local_index;
505            }
506
507            let joint = &mut world.joints[joint_id as usize];
508            joint.set_index = sleep_set_id;
509            joint.color_index = NULL_INDEX;
510            joint.local_index = sleep_joint_index;
511        }
512    }
513
514    // move island struct
515    {
516        debug_assert!(world.islands[island_id as usize].set_index == AWAKE_SET);
517
518        let island_index = world.islands[island_id as usize].local_index;
519        world.solver_sets[sleep_set_id as usize]
520            .island_sims
521            .push(IslandSim { island_id });
522
523        let moved_island_index = world.solver_sets[AWAKE_SET as usize].island_sims.len() as i32 - 1;
524        world.solver_sets[AWAKE_SET as usize]
525            .island_sims
526            .swap_remove(island_index as usize);
527        if moved_island_index != island_index {
528            // fix index on moved element
529            let moved_island_id =
530                world.solver_sets[AWAKE_SET as usize].island_sims[island_index as usize].island_id;
531            let moved_island = &mut world.islands[moved_island_id as usize];
532            debug_assert!(moved_island.local_index == moved_island_index);
533            moved_island.local_index = island_index;
534        }
535
536        let island = &mut world.islands[island_id as usize];
537        island.set_index = sleep_set_id;
538        island.local_index = 0;
539    }
540
541    if world.split_island_id == island_id {
542        world.split_island_id = NULL_INDEX;
543    }
544
545    world.validate_solver_sets();
546}
547
548/// This is called when joints are created between sets. I want to allow the
549/// sets to continue sleeping if both are asleep. Otherwise one set is waked.
550/// Islands will get merged when the set is waked. (b2MergeSolverSets)
551// bring-up: called by the joint slice (b2CreateJoint between sleeping sets).
552#[allow(dead_code)]
553pub fn merge_solver_sets(world: &mut World, set_id1: i32, set_id2: i32) {
554    debug_assert!(set_id1 >= FIRST_SLEEPING_SET);
555    debug_assert!(set_id2 >= FIRST_SLEEPING_SET);
556
557    // Move the fewest number of bodies
558    let (set_id1, set_id2) = {
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        if count1 < count2 {
562            (set_id2, set_id1)
563        } else {
564            (set_id1, set_id2)
565        }
566    };
567
568    // transfer bodies
569    {
570        let body_count = world.solver_sets[set_id2 as usize].body_sims.len();
571        for i in 0..body_count {
572            let sim_src = world.solver_sets[set_id2 as usize].body_sims[i];
573            let target_count = world.solver_sets[set_id1 as usize].body_sims.len() as i32;
574            {
575                let body = &mut world.bodies[sim_src.body_id as usize];
576                debug_assert!(body.set_index == set_id2);
577                body.set_index = set_id1;
578                body.local_index = target_count;
579            }
580            world.solver_sets[set_id1 as usize].body_sims.push(sim_src);
581        }
582    }
583
584    // transfer contacts
585    {
586        let contact_count = world.solver_sets[set_id2 as usize].contact_sims.len();
587        for i in 0..contact_count {
588            let contact_src = world.solver_sets[set_id2 as usize].contact_sims[i];
589            let target_count = world.solver_sets[set_id1 as usize].contact_sims.len() as i32;
590            {
591                let contact = &mut world.contacts[contact_src.contact_id as usize];
592                debug_assert!(contact.set_index == set_id2);
593                contact.set_index = set_id1;
594                contact.local_index = target_count;
595            }
596            world.solver_sets[set_id1 as usize]
597                .contact_sims
598                .push(contact_src);
599        }
600    }
601
602    // transfer joints
603    {
604        let joint_count = world.solver_sets[set_id2 as usize].joint_sims.len();
605        for i in 0..joint_count {
606            let joint_src = world.solver_sets[set_id2 as usize].joint_sims[i];
607            let target_count = world.solver_sets[set_id1 as usize].joint_sims.len() as i32;
608            {
609                let joint = &mut world.joints[joint_src.joint_id as usize];
610                debug_assert!(joint.set_index == set_id2);
611                joint.set_index = set_id1;
612                joint.local_index = target_count;
613            }
614            world.solver_sets[set_id1 as usize]
615                .joint_sims
616                .push(joint_src);
617        }
618    }
619
620    // transfer islands
621    {
622        let island_count = world.solver_sets[set_id2 as usize].island_sims.len();
623        for i in 0..island_count {
624            let island_src = world.solver_sets[set_id2 as usize].island_sims[i];
625            let target_count = world.solver_sets[set_id1 as usize].island_sims.len() as i32;
626            {
627                let island = &mut world.islands[island_src.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 between solver sets. (b2TransferBody — C passes set pointers;
644/// the Rust port passes set indices.)
645// bring-up: called by enable/disable body and destroy_body slices.
646#[allow(dead_code)]
647pub fn transfer_body(
648    world: &mut World,
649    target_set_index: i32,
650    source_set_index: i32,
651    body_id: i32,
652) {
653    if target_set_index == source_set_index {
654        return;
655    }
656
657    let source_index = world.bodies[body_id as usize].local_index;
658    let mut sim = world.solver_sets[source_set_index as usize].body_sims[source_index as usize];
659
660    let target_index = world.solver_sets[target_set_index as usize].body_sims.len() as i32;
661
662    // Clear transient body flags
663    sim.flags &=
664        !(body_flags::IS_FAST | body_flags::IS_SPEED_CAPPED | body_flags::HAD_TIME_OF_IMPACT);
665    world.solver_sets[target_set_index as usize]
666        .body_sims
667        .push(sim);
668
669    // Remove body sim from solver set that owns it
670    remove_body_sim(
671        &mut world.solver_sets[source_set_index as usize].body_sims,
672        &mut world.bodies,
673        source_index,
674    );
675
676    if source_set_index == AWAKE_SET {
677        world.solver_sets[AWAKE_SET as usize]
678            .body_states
679            .swap_remove(source_index as usize);
680    } else if target_set_index == AWAKE_SET {
681        let mut state = IDENTITY_BODY_STATE;
682        state.flags = world.bodies[body_id as usize].flags;
683        world.solver_sets[AWAKE_SET as usize]
684            .body_states
685            .push(state);
686    }
687
688    let body = &mut world.bodies[body_id as usize];
689    body.set_index = target_set_index;
690    body.local_index = target_index;
691}
692
693/// Move a joint between solver sets. (b2TransferJoint — C passes set pointers;
694/// the Rust port passes set indices.)
695// bring-up: called by the joint slice.
696#[allow(dead_code)]
697pub fn transfer_joint(
698    world: &mut World,
699    target_set_index: i32,
700    source_set_index: i32,
701    joint_id: i32,
702) {
703    if target_set_index == source_set_index {
704        return;
705    }
706
707    let (local_index, color_index) = {
708        let joint = &world.joints[joint_id as usize];
709        (joint.local_index, joint.color_index)
710    };
711
712    // Retrieve source.
713    let source_sim = if source_set_index == AWAKE_SET {
714        debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
715        world.constraint_graph.colors[color_index as usize].joint_sims[local_index as usize]
716    } else {
717        debug_assert!(color_index == NULL_INDEX);
718        world.solver_sets[source_set_index as usize].joint_sims[local_index as usize]
719    };
720
721    // Create target and copy. Fix joint.
722    if target_set_index == AWAKE_SET {
723        add_joint_to_graph(world, source_sim, joint_id);
724        world.joints[joint_id as usize].set_index = AWAKE_SET;
725    } else {
726        let target_local = world.solver_sets[target_set_index as usize]
727            .joint_sims
728            .len() as i32;
729        {
730            let joint = &mut world.joints[joint_id as usize];
731            joint.set_index = target_set_index;
732            joint.local_index = target_local;
733            joint.color_index = NULL_INDEX;
734        }
735        world.solver_sets[target_set_index as usize]
736            .joint_sims
737            .push(source_sim);
738    }
739
740    // Destroy source.
741    if source_set_index == AWAKE_SET {
742        let (body_id_a, body_id_b) = {
743            let joint = &world.joints[joint_id as usize];
744            (joint.edges[0].body_id, joint.edges[1].body_id)
745        };
746        remove_joint_from_graph(world, body_id_a, body_id_b, color_index, local_index);
747    } else {
748        let moved_index = world.solver_sets[source_set_index as usize]
749            .joint_sims
750            .len() as i32
751            - 1;
752        world.solver_sets[source_set_index as usize]
753            .joint_sims
754            .swap_remove(local_index as usize);
755        if moved_index != local_index {
756            // fix swapped element
757            let moved_id = world.solver_sets[source_set_index as usize].joint_sims
758                [local_index as usize]
759                .joint_id;
760            world.joints[moved_id as usize].local_index = local_index;
761        }
762    }
763}