Skip to main content

box3d_rust/
island.rs

1// Port of the island data model from box3d-cpp-reference/src/island.h
2// plus create/destroy/validate from island.c needed by body lifecycle.
3//
4// SPDX-FileCopyrightText: 2025 Erin Catto
5// SPDX-License-Identifier: MIT
6
7use crate::core::NULL_INDEX;
8use crate::solver_set::{AWAKE_SET, DISABLED_SET, FIRST_SLEEPING_SET, STATIC_SET};
9use crate::world::World;
10
11/// Cached contact data stored in the island for fast contiguous iteration.
12/// Avoids touching Contact during union-find in island splitting.
13/// (b3ContactLink)
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ContactLink {
16    pub contact_id: i32,
17    pub body_id_a: i32,
18    pub body_id_b: i32,
19}
20
21/// Cached joint data stored in the island for fast contiguous iteration.
22/// (b3JointLink)
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct JointLink {
25    pub joint_id: i32,
26    pub body_id_a: i32,
27    pub body_id_b: i32,
28}
29
30/// Persistent island for awake bodies, joints, and contacts. Contacts are
31/// touching. Contacts and joints may connect to static bodies, but static
32/// bodies are not in the island. (b3Island)
33///
34/// <https://en.wikipedia.org/wiki/Component_(graph_theory)>
35/// <https://en.wikipedia.org/wiki/Dynamic_connectivity>
36#[derive(Debug, Clone)]
37pub struct Island {
38    /// Index of solver set stored in World. May be NULL_INDEX.
39    pub set_index: i32,
40
41    /// Island index within set. May be NULL_INDEX.
42    pub local_index: i32,
43
44    pub island_id: i32,
45
46    /// How many contacts have been removed from this island. Used to determine
47    /// if an island is a candidate for splitting.
48    pub constraint_remove_count: i32,
49
50    pub bodies: Vec<i32>,
51
52    /// Contacts and joints that belong to this island. May connect to static
53    /// bodies not in the island. Each link carries the two body ids so island
54    /// splitting's union-find never needs to touch Contact/Joint.
55    pub contacts: Vec<ContactLink>,
56    pub joints: Vec<JointLink>,
57}
58
59impl Default for Island {
60    fn default() -> Self {
61        Island {
62            set_index: NULL_INDEX,
63            local_index: NULL_INDEX,
64            island_id: NULL_INDEX,
65            constraint_remove_count: 0,
66            bodies: Vec::new(),
67            contacts: Vec::new(),
68            joints: Vec::new(),
69        }
70    }
71}
72
73/// Used to move islands across solver sets. (b3IslandSim)
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct IslandSim {
76    pub island_id: i32,
77}
78
79impl Default for IslandSim {
80    fn default() -> Self {
81        IslandSim {
82            island_id: NULL_INDEX,
83        }
84    }
85}
86
87/// Create an empty island in the given set. Returns the island id.
88/// (b3CreateIsland — C returns a pointer; Rust returns the id)
89pub fn create_island(world: &mut World, set_index: i32) -> i32 {
90    debug_assert!(set_index == AWAKE_SET || set_index >= FIRST_SLEEPING_SET);
91
92    let island_id = world.island_id_pool.alloc_id();
93
94    if island_id == world.islands.len() as i32 {
95        world.islands.push(Island::default());
96    } else {
97        debug_assert!(world.islands[island_id as usize].set_index == NULL_INDEX);
98    }
99
100    let set = &mut world.solver_sets[set_index as usize];
101    let local_index = set.island_sims.len() as i32;
102    set.island_sims.push(IslandSim { island_id });
103
104    let island = &mut world.islands[island_id as usize];
105    island.set_index = set_index;
106    island.local_index = local_index;
107    island.island_id = island_id;
108    island.bodies = Vec::new();
109    island.contacts = Vec::new();
110    island.joints = Vec::new();
111    island.constraint_remove_count = 0;
112
113    island_id
114}
115
116/// (b3DestroyIsland)
117pub fn destroy_island(world: &mut World, island_id: i32) {
118    if world.split_island_id == island_id {
119        world.split_island_id = NULL_INDEX;
120    }
121
122    let (set_index, local_index) = {
123        let island = &world.islands[island_id as usize];
124        (island.set_index, island.local_index)
125    };
126    let set = &mut world.solver_sets[set_index as usize];
127    {
128        let last_index = set.island_sims.len() - 1;
129        debug_assert!(0 <= local_index && (local_index as usize) <= last_index);
130        let move_island_id = set.island_sims[last_index].island_id;
131        set.island_sims.swap_remove(local_index as usize);
132        world.islands[move_island_id as usize].local_index = local_index;
133    }
134
135    let island = &mut world.islands[island_id as usize];
136    island.bodies = Vec::new();
137    island.contacts = Vec::new();
138    island.joints = Vec::new();
139    island.constraint_remove_count = 0;
140    island.local_index = NULL_INDEX;
141    island.island_id = NULL_INDEX;
142    island.set_index = NULL_INDEX;
143
144    world.island_id_pool.free_id(island_id);
145}
146
147/// Merge two islands, keeping the larger. Either id may be NULL_INDEX (static).
148/// (b3MergeIslands)
149fn merge_islands(world: &mut World, island_id_a: i32, island_id_b: i32) -> i32 {
150    if island_id_a == island_id_b {
151        return island_id_a;
152    }
153    if island_id_a == NULL_INDEX {
154        debug_assert!(island_id_b != NULL_INDEX);
155        return island_id_b;
156    }
157    if island_id_b == NULL_INDEX {
158        debug_assert!(island_id_a != NULL_INDEX);
159        return island_id_a;
160    }
161
162    let (big_id, small_id) = {
163        let count_a = world.islands[island_id_a as usize].bodies.len();
164        let count_b = world.islands[island_id_b as usize].bodies.len();
165        if count_a >= count_b {
166            (island_id_a, island_id_b)
167        } else {
168            (island_id_b, island_id_a)
169        }
170    };
171
172    let small_bodies = std::mem::take(&mut world.islands[small_id as usize].bodies);
173    for body_id in small_bodies {
174        debug_assert!(world.bodies[body_id as usize].island_id == small_id);
175        let island_index = world.islands[big_id as usize].bodies.len() as i32;
176        world.bodies[body_id as usize].island_id = big_id;
177        world.bodies[body_id as usize].island_index = island_index;
178        world.islands[big_id as usize].bodies.push(body_id);
179    }
180
181    let small_contacts = std::mem::take(&mut world.islands[small_id as usize].contacts);
182    for link in small_contacts {
183        let contact = &mut world.contacts[link.contact_id as usize];
184        contact.island_id = big_id;
185        contact.island_index = world.islands[big_id as usize].contacts.len() as i32;
186        world.islands[big_id as usize].contacts.push(link);
187    }
188
189    let small_joints = std::mem::take(&mut world.islands[small_id as usize].joints);
190    for link in small_joints {
191        let joint = &mut world.joints[link.joint_id as usize];
192        joint.island_id = big_id;
193        joint.island_index = world.islands[big_id as usize].joints.len() as i32;
194        world.islands[big_id as usize].joints.push(link);
195    }
196
197    world.islands[big_id as usize].constraint_remove_count +=
198        world.islands[small_id as usize].constraint_remove_count;
199
200    destroy_island(world, small_id);
201    validate_island(world, big_id);
202    big_id
203}
204
205/// (b3AddContactToIsland)
206fn add_contact_to_island(world: &mut World, island_id: i32, contact_id: i32) {
207    debug_assert!(world.contacts[contact_id as usize].island_id == NULL_INDEX);
208    debug_assert!(world.contacts[contact_id as usize].island_index == NULL_INDEX);
209
210    let island_index = world.islands[island_id as usize].contacts.len() as i32;
211    let link = ContactLink {
212        contact_id,
213        body_id_a: world.contacts[contact_id as usize].edges[0].body_id,
214        body_id_b: world.contacts[contact_id as usize].edges[1].body_id,
215    };
216
217    world.contacts[contact_id as usize].island_id = island_id;
218    world.contacts[contact_id as usize].island_index = island_index;
219    world.islands[island_id as usize].contacts.push(link);
220
221    validate_island(world, island_id);
222}
223
224/// Link a touching contact into an island, waking sleeping partners and merging
225/// as needed. (b3LinkContact)
226pub fn link_contact(world: &mut World, contact_id: i32) {
227    use crate::contact::contact_flags;
228    use crate::solver_set::wake_solver_set;
229
230    debug_assert!((world.contacts[contact_id as usize].flags & contact_flags::TOUCHING) != 0);
231
232    let body_id_a = world.contacts[contact_id as usize].edges[0].body_id;
233    let body_id_b = world.contacts[contact_id as usize].edges[1].body_id;
234
235    let set_a = world.bodies[body_id_a as usize].set_index;
236    let set_b = world.bodies[body_id_b as usize].set_index;
237    debug_assert!(set_a != DISABLED_SET && set_b != DISABLED_SET);
238    debug_assert!(set_a != STATIC_SET || set_b != STATIC_SET);
239
240    // Wake bodyB if bodyA is awake and bodyB is sleeping
241    if set_a == AWAKE_SET && set_b >= FIRST_SLEEPING_SET {
242        wake_solver_set(world, set_b);
243    }
244
245    // Wake bodyA if bodyB is awake and bodyA is sleeping
246    let set_a = world.bodies[body_id_a as usize].set_index;
247    let set_b = world.bodies[body_id_b as usize].set_index;
248    if set_b == AWAKE_SET && set_a >= FIRST_SLEEPING_SET {
249        wake_solver_set(world, set_a);
250    }
251
252    let island_id_a = world.bodies[body_id_a as usize].island_id;
253    let island_id_b = world.bodies[body_id_b as usize].island_id;
254
255    debug_assert!(
256        world.bodies[body_id_a as usize].set_index != STATIC_SET || island_id_a == NULL_INDEX
257    );
258    debug_assert!(
259        world.bodies[body_id_b as usize].set_index != STATIC_SET || island_id_b == NULL_INDEX
260    );
261    debug_assert!(island_id_a != NULL_INDEX || island_id_b != NULL_INDEX);
262
263    let final_island_id = merge_islands(world, island_id_a, island_id_b);
264    add_contact_to_island(world, final_island_id, contact_id);
265}
266
267/// Remove a contact from its island. (b3UnlinkContact)
268pub fn unlink_contact(world: &mut World, contact_id: i32) {
269    let island_id = world.contacts[contact_id as usize].island_id;
270    debug_assert!(island_id != NULL_INDEX);
271
272    let remove_index = world.contacts[contact_id as usize].island_index;
273    let island = &mut world.islands[island_id as usize];
274    debug_assert!(0 <= remove_index && (remove_index as usize) < island.contacts.len());
275    debug_assert!(island.contacts[remove_index as usize].contact_id == contact_id);
276
277    let moved_index = island.contacts.len() as i32 - 1;
278    island.contacts.swap_remove(remove_index as usize);
279    if moved_index != remove_index {
280        let moved_contact_id = island.contacts[remove_index as usize].contact_id;
281        debug_assert!(world.contacts[moved_contact_id as usize].island_index == moved_index);
282        world.contacts[moved_contact_id as usize].island_index = remove_index;
283    }
284
285    world.contacts[contact_id as usize].island_id = NULL_INDEX;
286    world.contacts[contact_id as usize].island_index = NULL_INDEX;
287    world.islands[island_id as usize].constraint_remove_count += 1;
288
289    validate_island(world, island_id);
290}
291
292/// (b3AddJointToIsland)
293fn add_joint_to_island(world: &mut World, island_id: i32, joint_id: i32) {
294    debug_assert!(world.joints[joint_id as usize].island_id == NULL_INDEX);
295    debug_assert!(world.joints[joint_id as usize].island_index == NULL_INDEX);
296
297    let island_index = world.islands[island_id as usize].joints.len() as i32;
298    let link = JointLink {
299        joint_id,
300        body_id_a: world.joints[joint_id as usize].edges[0].body_id,
301        body_id_b: world.joints[joint_id as usize].edges[1].body_id,
302    };
303
304    world.joints[joint_id as usize].island_id = island_id;
305    world.joints[joint_id as usize].island_index = island_index;
306    world.islands[island_id as usize].joints.push(link);
307
308    validate_island(world, island_id);
309}
310
311/// Link a joint into the island graph when it is created. (b3LinkJoint)
312pub fn link_joint(world: &mut World, joint_id: i32) {
313    use crate::solver_set::wake_solver_set;
314    use crate::types::BodyType;
315
316    let body_id_a = world.joints[joint_id as usize].edges[0].body_id;
317    let body_id_b = world.joints[joint_id as usize].edges[1].body_id;
318
319    debug_assert!(
320        world.bodies[body_id_a as usize].type_ == BodyType::Dynamic
321            || world.bodies[body_id_b as usize].type_ == BodyType::Dynamic
322    );
323
324    let set_a = world.bodies[body_id_a as usize].set_index;
325    let set_b = world.bodies[body_id_b as usize].set_index;
326
327    if set_a == AWAKE_SET && set_b >= FIRST_SLEEPING_SET {
328        wake_solver_set(world, set_b);
329    } else if set_b == AWAKE_SET && set_a >= FIRST_SLEEPING_SET {
330        wake_solver_set(world, set_a);
331    }
332
333    let island_id_a = world.bodies[body_id_a as usize].island_id;
334    let island_id_b = world.bodies[body_id_b as usize].island_id;
335
336    debug_assert!(island_id_a != NULL_INDEX || island_id_b != NULL_INDEX);
337
338    // Merge islands. This will destroy one of the islands.
339    let final_island_id = merge_islands(world, island_id_a, island_id_b);
340
341    // Add joint to the island that survived
342    add_joint_to_island(world, final_island_id, joint_id);
343}
344
345/// Unlink a joint from the island graph when it is destroyed. (b3UnlinkJoint)
346pub fn unlink_joint(world: &mut World, joint_id: i32) {
347    let island_id = world.joints[joint_id as usize].island_id;
348    if island_id == NULL_INDEX {
349        return;
350    }
351
352    let remove_index = world.joints[joint_id as usize].island_index;
353    let island = &mut world.islands[island_id as usize];
354    debug_assert!(0 <= remove_index && (remove_index as usize) < island.joints.len());
355    debug_assert!(island.joints[remove_index as usize].joint_id == joint_id);
356
357    let moved_index = island.joints.len() as i32 - 1;
358    island.joints.swap_remove(remove_index as usize);
359    if moved_index != remove_index {
360        // Fix islandIndex on the joint that was swapped into removeIndex
361        let moved_joint_id = island.joints[remove_index as usize].joint_id;
362        debug_assert!(world.joints[moved_joint_id as usize].island_index == moved_index);
363        world.joints[moved_joint_id as usize].island_index = remove_index;
364    }
365
366    world.joints[joint_id as usize].island_id = NULL_INDEX;
367    world.joints[joint_id as usize].island_index = NULL_INDEX;
368    world.islands[island_id as usize].constraint_remove_count += 1;
369
370    validate_island(world, island_id);
371}
372
373/// Find parent of a node. Use path halving to speed up further queries.
374/// (b3IslandFindParent)
375fn island_find_parent(parents: &mut [i32], mut node: i32) -> i32 {
376    // Walk the chain of parents to find the node that is its own parent (the root)
377    while parents[node as usize] != node {
378        let grand_parent = parents[parents[node as usize] as usize];
379        parents[node as usize] = grand_parent;
380        node = grand_parent;
381    }
382
383    node
384}
385
386/// Connect the components containing node1 and node2.
387/// Uses rank to keep tree balanced. Tracks per-component contact and joint counts.
388/// (b3IslandUnion)
389fn island_union(
390    parents: &mut [i32],
391    ranks: &mut [i32],
392    node1: i32,
393    node2: i32,
394    contact_counts: &mut [i32],
395    joint_counts: &mut [i32],
396) {
397    let root1 = island_find_parent(parents, node1) as usize;
398    let root2 = island_find_parent(parents, node2) as usize;
399    if root1 != root2 {
400        if ranks[root1] < ranks[root2] {
401            parents[root1] = root2 as i32;
402            contact_counts[root2] += contact_counts[root1];
403            joint_counts[root2] += joint_counts[root1];
404        } else if ranks[root1] > ranks[root2] {
405            parents[root2] = root1 as i32;
406            contact_counts[root1] += contact_counts[root2];
407            joint_counts[root1] += joint_counts[root2];
408        } else {
409            parents[root2] = root1 as i32;
410            ranks[root1] += 1;
411            contact_counts[root1] += contact_counts[root2];
412            joint_counts[root1] += joint_counts[root2];
413        }
414    }
415}
416
417/// Split an island because some contacts and/or joints have been removed.
418/// This uses union find and touches a lot of memory, so it can be slow.
419/// (b3SplitIsland)
420///
421/// Note: contacts/joints connected to static bodies must belong to an island
422/// but don't affect island connectivity.
423/// Note: static bodies are never in an island.
424///
425/// <https://en.wikipedia.org/wiki/Disjoint-set_data_structure>
426pub fn split_island(world: &mut World, base_id: i32) {
427    debug_assert!(world.islands[base_id as usize].constraint_remove_count > 0);
428    debug_assert!(world.islands[base_id as usize].set_index == AWAKE_SET);
429
430    validate_island(world, base_id);
431
432    // Take the base island's arrays. C detaches the raw buffers so
433    // b3DestroyIsland won't free them; mem::take is the Rust equivalent.
434    let base_body_ids = std::mem::take(&mut world.islands[base_id as usize].bodies);
435    let base_contacts = std::mem::take(&mut world.islands[base_id as usize].contacts);
436    let base_joints = std::mem::take(&mut world.islands[base_id as usize].joints);
437
438    let base_body_count = base_body_ids.len();
439
440    // C allocates the union-find scratch from the arena; the Rust step scratch
441    // is plain Vecs.
442    let mut parents: Vec<i32> = (0..base_body_count as i32).collect();
443    let mut contact_counts: Vec<i32> = vec![0; base_body_count];
444    let mut joint_counts: Vec<i32> = vec![0; base_body_count];
445    let mut ranks: Vec<i32> = vec![0; base_body_count];
446
447    // Union over contacts, tracking per-component contact counts
448    for link in &base_contacts {
449        debug_assert!(0 <= link.body_id_a && (link.body_id_a as usize) < world.bodies.len());
450        debug_assert!(0 <= link.body_id_b && (link.body_id_b as usize) < world.bodies.len());
451        let island_index_a = world.bodies[link.body_id_a as usize].island_index;
452        let island_index_b = world.bodies[link.body_id_b as usize].island_index;
453
454        // Only connect non-static bodies
455        if island_index_a != NULL_INDEX && island_index_b != NULL_INDEX {
456            debug_assert!(0 <= island_index_a && (island_index_a as usize) < base_body_count);
457            debug_assert!(0 <= island_index_b && (island_index_b as usize) < base_body_count);
458            island_union(
459                &mut parents,
460                &mut ranks,
461                island_index_a,
462                island_index_b,
463                &mut contact_counts,
464                &mut joint_counts,
465            );
466            let root = island_find_parent(&mut parents, island_index_a);
467            contact_counts[root as usize] += 1;
468        } else {
469            let island_index = if island_index_a != NULL_INDEX {
470                island_index_a
471            } else {
472                island_index_b
473            };
474            let root = island_find_parent(&mut parents, island_index);
475            contact_counts[root as usize] += 1;
476        }
477    }
478
479    // Union over joints, tracking per-component joint counts
480    for link in &base_joints {
481        debug_assert!(0 <= link.body_id_a && (link.body_id_a as usize) < world.bodies.len());
482        debug_assert!(0 <= link.body_id_b && (link.body_id_b as usize) < world.bodies.len());
483        let island_index_a = world.bodies[link.body_id_a as usize].island_index;
484        let island_index_b = world.bodies[link.body_id_b as usize].island_index;
485
486        // Only connect non-static bodies
487        if island_index_a != NULL_INDEX && island_index_b != NULL_INDEX {
488            debug_assert!(0 <= island_index_a && (island_index_a as usize) < base_body_count);
489            debug_assert!(0 <= island_index_b && (island_index_b as usize) < base_body_count);
490            island_union(
491                &mut parents,
492                &mut ranks,
493                island_index_a,
494                island_index_b,
495                &mut contact_counts,
496                &mut joint_counts,
497            );
498            let root = island_find_parent(&mut parents, island_index_a);
499            joint_counts[root as usize] += 1;
500        } else {
501            let island_index = if island_index_a != NULL_INDEX {
502                island_index_a
503            } else {
504                island_index_b
505            };
506            let root = island_find_parent(&mut parents, island_index);
507            joint_counts[root as usize] += 1;
508        }
509    }
510
511    // Done with ranks
512    drop(ranks);
513
514    // Flatten all parent indices and count connected components.
515    let mut component_count = 0;
516    for i in 0..base_body_count {
517        parents[i] = island_find_parent(&mut parents, i as i32);
518        if parents[i] == i as i32 {
519            component_count += 1;
520        }
521    }
522
523    // Early return — island is still fully connected, no split needed.
524    if component_count == 1 {
525        let base_island = &mut world.islands[base_id as usize];
526        base_island.constraint_remove_count = 0;
527        base_island.bodies = base_body_ids;
528        base_island.contacts = base_contacts;
529        base_island.joints = base_joints;
530        return;
531    }
532
533    // Map from body index to new island index. Only set for root bodies.
534    let mut root_map: Vec<i32> = vec![NULL_INDEX; base_body_count];
535
536    let mut component_body_counts: Vec<i32> = vec![0; component_count];
537    let mut component_contact_counts: Vec<i32> = vec![0; component_count];
538    let mut component_joint_counts: Vec<i32> = vec![0; component_count];
539    let mut island_count = 0usize;
540
541    // Find the root body for each body and create islands as needed.
542    // Extract per-component counts from the root nodes' accumulated counts.
543    for &parent in parents.iter().take(base_body_count) {
544        let root_index = parent as usize;
545        if root_map[root_index] == NULL_INDEX {
546            root_map[root_index] = island_count as i32;
547            component_body_counts[island_count] = 0;
548            component_contact_counts[island_count] = contact_counts[root_index];
549            component_joint_counts[island_count] = joint_counts[root_index];
550            island_count += 1;
551        }
552
553        component_body_counts[root_map[root_index] as usize] += 1;
554    }
555
556    debug_assert!(island_count == component_count);
557
558    // Map from new island index to island id
559    let mut island_ids: Vec<i32> = Vec::with_capacity(island_count);
560
561    // Create new islands and reserve body/contact/joint arrays
562    for i in 0..island_count {
563        let new_island_id = create_island(world, AWAKE_SET);
564        island_ids.push(new_island_id);
565
566        // Reserve arrays to avoid wasteful growth.
567        let new_island = &mut world.islands[new_island_id as usize];
568        new_island.bodies.reserve(component_body_counts[i] as usize);
569        new_island
570            .contacts
571            .reserve(component_contact_counts[i] as usize);
572        new_island
573            .joints
574            .reserve(component_joint_counts[i] as usize);
575    }
576
577    // Assign bodies to new islands
578    for (i, &body_id) in base_body_ids.iter().enumerate() {
579        let root = island_find_parent(&mut parents, i as i32);
580        let new_island_id = island_ids[root_map[root as usize] as usize];
581
582        let island_index = world.islands[new_island_id as usize].bodies.len() as i32;
583        debug_assert!(
584            (island_index as usize) < world.islands[new_island_id as usize].bodies.capacity()
585        );
586        world.islands[new_island_id as usize].bodies.push(body_id);
587
588        let body = &mut world.bodies[body_id as usize];
589        body.island_id = new_island_id;
590        body.island_index = island_index;
591    }
592
593    // Assign contacts to the island of their bodies
594    for link in &base_contacts {
595        // Static bodies don't have an island id.
596        let island_id_a = world.bodies[link.body_id_a as usize].island_id;
597        let target_island_id = if island_id_a != NULL_INDEX {
598            island_id_a
599        } else {
600            world.bodies[link.body_id_b as usize].island_id
601        };
602
603        let target_island = &mut world.islands[target_island_id as usize];
604        let island_index = target_island.contacts.len() as i32;
605        debug_assert!((island_index as usize) < target_island.contacts.capacity());
606        target_island.contacts.push(*link);
607
608        let contact = &mut world.contacts[link.contact_id as usize];
609        contact.island_id = target_island_id;
610        contact.island_index = island_index;
611    }
612
613    // Assign joints to the island of their bodies
614    for link in &base_joints {
615        // Static bodies don't have an island id.
616        let island_id_a = world.bodies[link.body_id_a as usize].island_id;
617        let target_island_id = if island_id_a != NULL_INDEX {
618            island_id_a
619        } else {
620            world.bodies[link.body_id_b as usize].island_id
621        };
622
623        let target_island = &mut world.islands[target_island_id as usize];
624        let island_index = target_island.joints.len() as i32;
625        debug_assert!((island_index as usize) < target_island.joints.capacity());
626        target_island.joints.push(*link);
627
628        let joint = &mut world.joints[link.joint_id as usize];
629        joint.island_id = target_island_id;
630        joint.island_index = island_index;
631    }
632
633    // Destroy the base island
634    destroy_island(world, base_id);
635}
636
637/// Validate island connectivity and bookkeeping. (b3ValidateIsland)
638///
639/// C compiles this only with B3_VALIDATE; here it always runs and asserts in
640/// debug builds.
641pub fn validate_island(world: &World, island_id: i32) {
642    if island_id == NULL_INDEX {
643        return;
644    }
645
646    let island = &world.islands[island_id as usize];
647    debug_assert!(island.island_id == island_id);
648    debug_assert!(island.set_index != NULL_INDEX);
649
650    {
651        debug_assert!(!island.bodies.is_empty());
652        debug_assert!(island.bodies.len() as i32 <= world.body_id_pool.id_count());
653
654        for (i, &body_id) in island.bodies.iter().enumerate() {
655            let body = &world.bodies[body_id as usize];
656            debug_assert!(body.island_id == island_id);
657            debug_assert!(body.island_index == i as i32);
658            debug_assert!(body.set_index == island.set_index);
659            let _ = (body, i);
660        }
661    }
662
663    if !island.contacts.is_empty() {
664        debug_assert!(island.contacts.len() as i32 <= world.contact_id_pool.id_count());
665
666        for (i, link) in island.contacts.iter().enumerate() {
667            let contact = &world.contacts[link.contact_id as usize];
668            debug_assert!(contact.set_index == island.set_index);
669            debug_assert!(contact.island_id == island_id);
670            debug_assert!(contact.island_index == i as i32);
671            let _ = (contact, i);
672        }
673    }
674
675    if !island.joints.is_empty() {
676        debug_assert!(island.joints.len() as i32 <= world.joint_id_pool.id_count());
677
678        for (i, link) in island.joints.iter().enumerate() {
679            let joint = &world.joints[link.joint_id as usize];
680            debug_assert!(joint.set_index == island.set_index);
681            debug_assert!(joint.island_id == island_id);
682            debug_assert!(joint.island_index == i as i32);
683            let _ = (joint, i);
684        }
685    }
686}