box2d-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +336 -0
  4. data/examples/contact_events.rb +22 -0
  5. data/examples/debug_draw.rb +15 -0
  6. data/examples/falling_ball.rb +19 -0
  7. data/examples/rugl_debug_draw.rb +68 -0
  8. data/examples/stagecraft_debug_draw.rb +64 -0
  9. data/examples/support/box2d_debug_lines.rb +125 -0
  10. data/ext/box2d/CMakeLists.txt +35 -0
  11. data/ext/box2d/extconf.rb +42 -0
  12. data/ext/box2d/native.c +9 -0
  13. data/ext/box2d/vendor/box2d/LICENSE +21 -0
  14. data/ext/box2d/vendor/box2d/VERSION +1 -0
  15. data/ext/box2d/vendor/box2d/include/box2d/base.h +131 -0
  16. data/ext/box2d/vendor/box2d/include/box2d/box2d.h +1222 -0
  17. data/ext/box2d/vendor/box2d/include/box2d/collision.h +830 -0
  18. data/ext/box2d/vendor/box2d/include/box2d/id.h +144 -0
  19. data/ext/box2d/vendor/box2d/include/box2d/math_functions.h +761 -0
  20. data/ext/box2d/vendor/box2d/include/box2d/types.h +1457 -0
  21. data/ext/box2d/vendor/box2d/src/CMakeLists.txt +223 -0
  22. data/ext/box2d/vendor/box2d/src/aabb.c +132 -0
  23. data/ext/box2d/vendor/box2d/src/aabb.h +56 -0
  24. data/ext/box2d/vendor/box2d/src/arena_allocator.c +112 -0
  25. data/ext/box2d/vendor/box2d/src/arena_allocator.h +48 -0
  26. data/ext/box2d/vendor/box2d/src/array.c +8 -0
  27. data/ext/box2d/vendor/box2d/src/array.h +179 -0
  28. data/ext/box2d/vendor/box2d/src/atomic.h +79 -0
  29. data/ext/box2d/vendor/box2d/src/bitset.c +67 -0
  30. data/ext/box2d/vendor/box2d/src/bitset.h +65 -0
  31. data/ext/box2d/vendor/box2d/src/body.c +1884 -0
  32. data/ext/box2d/vendor/box2d/src/body.h +194 -0
  33. data/ext/box2d/vendor/box2d/src/box2d.natvis +41 -0
  34. data/ext/box2d/vendor/box2d/src/broad_phase.c +524 -0
  35. data/ext/box2d/vendor/box2d/src/broad_phase.h +83 -0
  36. data/ext/box2d/vendor/box2d/src/constants.h +54 -0
  37. data/ext/box2d/vendor/box2d/src/constraint_graph.c +322 -0
  38. data/ext/box2d/vendor/box2d/src/constraint_graph.h +58 -0
  39. data/ext/box2d/vendor/box2d/src/contact.c +650 -0
  40. data/ext/box2d/vendor/box2d/src/contact.h +148 -0
  41. data/ext/box2d/vendor/box2d/src/contact_solver.c +2120 -0
  42. data/ext/box2d/vendor/box2d/src/contact_solver.h +54 -0
  43. data/ext/box2d/vendor/box2d/src/core.c +178 -0
  44. data/ext/box2d/vendor/box2d/src/core.h +149 -0
  45. data/ext/box2d/vendor/box2d/src/ctz.h +112 -0
  46. data/ext/box2d/vendor/box2d/src/distance.c +1415 -0
  47. data/ext/box2d/vendor/box2d/src/distance_joint.c +556 -0
  48. data/ext/box2d/vendor/box2d/src/dynamic_tree.c +1989 -0
  49. data/ext/box2d/vendor/box2d/src/geometry.c +1028 -0
  50. data/ext/box2d/vendor/box2d/src/hull.c +328 -0
  51. data/ext/box2d/vendor/box2d/src/id_pool.c +79 -0
  52. data/ext/box2d/vendor/box2d/src/id_pool.h +35 -0
  53. data/ext/box2d/vendor/box2d/src/island.c +977 -0
  54. data/ext/box2d/vendor/box2d/src/island.h +89 -0
  55. data/ext/box2d/vendor/box2d/src/joint.c +1272 -0
  56. data/ext/box2d/vendor/box2d/src/joint.h +335 -0
  57. data/ext/box2d/vendor/box2d/src/manifold.c +1726 -0
  58. data/ext/box2d/vendor/box2d/src/math_functions.c +159 -0
  59. data/ext/box2d/vendor/box2d/src/motor_joint.c +283 -0
  60. data/ext/box2d/vendor/box2d/src/mouse_joint.c +214 -0
  61. data/ext/box2d/vendor/box2d/src/mover.c +73 -0
  62. data/ext/box2d/vendor/box2d/src/prismatic_joint.c +656 -0
  63. data/ext/box2d/vendor/box2d/src/revolute_joint.c +534 -0
  64. data/ext/box2d/vendor/box2d/src/sensor.c +389 -0
  65. data/ext/box2d/vendor/box2d/src/sensor.h +36 -0
  66. data/ext/box2d/vendor/box2d/src/shape.c +1714 -0
  67. data/ext/box2d/vendor/box2d/src/shape.h +123 -0
  68. data/ext/box2d/vendor/box2d/src/solver.c +2038 -0
  69. data/ext/box2d/vendor/box2d/src/solver.h +155 -0
  70. data/ext/box2d/vendor/box2d/src/solver_set.c +613 -0
  71. data/ext/box2d/vendor/box2d/src/solver_set.h +57 -0
  72. data/ext/box2d/vendor/box2d/src/table.c +238 -0
  73. data/ext/box2d/vendor/box2d/src/table.h +37 -0
  74. data/ext/box2d/vendor/box2d/src/timer.c +185 -0
  75. data/ext/box2d/vendor/box2d/src/types.c +151 -0
  76. data/ext/box2d/vendor/box2d/src/weld_joint.c +310 -0
  77. data/ext/box2d/vendor/box2d/src/wheel_joint.c +551 -0
  78. data/ext/box2d/vendor/box2d/src/world.c +3301 -0
  79. data/ext/box2d/vendor/box2d/src/world.h +192 -0
  80. data/generator/generate.rb +316 -0
  81. data/generator/layout_probe.c +507 -0
  82. data/generator/verify_layouts.rb +32 -0
  83. data/lib/box2d/body.rb +172 -0
  84. data/lib/box2d/body_definition.rb +38 -0
  85. data/lib/box2d/body_shapes.rb +135 -0
  86. data/lib/box2d/chain.rb +61 -0
  87. data/lib/box2d/debug_draw.rb +118 -0
  88. data/lib/box2d/events.rb +103 -0
  89. data/lib/box2d/fixed_stepper.rb +39 -0
  90. data/lib/box2d/handle.rb +46 -0
  91. data/lib/box2d/hit.rb +5 -0
  92. data/lib/box2d/joint.rb +197 -0
  93. data/lib/box2d/native.rb +1462 -0
  94. data/lib/box2d/native_loader.rb +45 -0
  95. data/lib/box2d/pixel_scale.rb +29 -0
  96. data/lib/box2d/shape.rb +109 -0
  97. data/lib/box2d/shape_definition.rb +44 -0
  98. data/lib/box2d/value_conversion.rb +80 -0
  99. data/lib/box2d/version.rb +7 -0
  100. data/lib/box2d/world.rb +135 -0
  101. data/lib/box2d/world_joints.rb +156 -0
  102. data/lib/box2d/world_queries.rb +122 -0
  103. data/lib/box2d/world_registry.rb +95 -0
  104. data/lib/box2d.rb +31 -0
  105. data/script/build_platform_gem.rb +24 -0
  106. data/script/deterministic_scene.rb +56 -0
  107. data/script/update_deterministic_snapshot.rb +11 -0
  108. data/script/verify_platform_gem.rb +24 -0
  109. metadata +164 -0
@@ -0,0 +1,977 @@
1
+ // SPDX-FileCopyrightText: 2023 Erin Catto
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ #include "island.h"
5
+
6
+ #include "body.h"
7
+ #include "contact.h"
8
+ #include "core.h"
9
+ #include "joint.h"
10
+ #include "solver_set.h"
11
+ #include "world.h"
12
+
13
+ #include <stddef.h>
14
+
15
+ B2_ARRAY_SOURCE( b2Island, b2Island )
16
+ B2_ARRAY_SOURCE( b2IslandSim, b2IslandSim )
17
+
18
+ b2Island* b2CreateIsland( b2World* world, int setIndex )
19
+ {
20
+ B2_ASSERT( setIndex == b2_awakeSet || setIndex >= b2_firstSleepingSet );
21
+
22
+ int islandId = b2AllocId( &world->islandIdPool );
23
+
24
+ if ( islandId == world->islands.count )
25
+ {
26
+ b2Island emptyIsland = { 0 };
27
+ b2IslandArray_Push( &world->islands, emptyIsland );
28
+ }
29
+ else
30
+ {
31
+ B2_ASSERT( world->islands.data[islandId].setIndex == B2_NULL_INDEX );
32
+ }
33
+
34
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, setIndex );
35
+
36
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
37
+ island->setIndex = setIndex;
38
+ island->localIndex = set->islandSims.count;
39
+ island->islandId = islandId;
40
+ island->headBody = B2_NULL_INDEX;
41
+ island->tailBody = B2_NULL_INDEX;
42
+ island->bodyCount = 0;
43
+ island->headContact = B2_NULL_INDEX;
44
+ island->tailContact = B2_NULL_INDEX;
45
+ island->contactCount = 0;
46
+ island->headJoint = B2_NULL_INDEX;
47
+ island->tailJoint = B2_NULL_INDEX;
48
+ island->jointCount = 0;
49
+ island->parentIsland = B2_NULL_INDEX;
50
+ island->constraintRemoveCount = 0;
51
+
52
+ b2IslandSim* islandSim = b2IslandSimArray_Add( &set->islandSims );
53
+ islandSim->islandId = islandId;
54
+
55
+ return island;
56
+ }
57
+
58
+ void b2DestroyIsland( b2World* world, int islandId )
59
+ {
60
+ if (world->splitIslandId == islandId)
61
+ {
62
+ world->splitIslandId = B2_NULL_INDEX;
63
+ }
64
+
65
+ // assume island is empty
66
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
67
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, island->setIndex );
68
+ int movedIndex = b2IslandSimArray_RemoveSwap( &set->islandSims, island->localIndex );
69
+ if ( movedIndex != B2_NULL_INDEX )
70
+ {
71
+ // Fix index on moved element
72
+ b2IslandSim* movedElement = set->islandSims.data + island->localIndex;
73
+ int movedId = movedElement->islandId;
74
+ b2Island* movedIsland = b2IslandArray_Get( &world->islands, movedId );
75
+ B2_ASSERT( movedIsland->localIndex == movedIndex );
76
+ movedIsland->localIndex = island->localIndex;
77
+ }
78
+
79
+ // Free island and id (preserve island revision)
80
+ island->islandId = B2_NULL_INDEX;
81
+ island->setIndex = B2_NULL_INDEX;
82
+ island->localIndex = B2_NULL_INDEX;
83
+ b2FreeId( &world->islandIdPool, islandId );
84
+ }
85
+
86
+ static void b2AddContactToIsland( b2World* world, int islandId, b2Contact* contact )
87
+ {
88
+ B2_ASSERT( contact->islandId == B2_NULL_INDEX );
89
+ B2_ASSERT( contact->islandPrev == B2_NULL_INDEX );
90
+ B2_ASSERT( contact->islandNext == B2_NULL_INDEX );
91
+
92
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
93
+
94
+ if ( island->headContact != B2_NULL_INDEX )
95
+ {
96
+ contact->islandNext = island->headContact;
97
+ b2Contact* headContact = b2ContactArray_Get( &world->contacts, island->headContact);
98
+ headContact->islandPrev = contact->contactId;
99
+ }
100
+
101
+ island->headContact = contact->contactId;
102
+ if ( island->tailContact == B2_NULL_INDEX )
103
+ {
104
+ island->tailContact = island->headContact;
105
+ }
106
+
107
+ island->contactCount += 1;
108
+ contact->islandId = islandId;
109
+
110
+ b2ValidateIsland( world, islandId );
111
+ }
112
+
113
+ // Link a contact into an island.
114
+ // This performs union-find and path compression to join islands.
115
+ // https://en.wikipedia.org/wiki/Disjoint-set_data_structure
116
+ void b2LinkContact( b2World* world, b2Contact* contact )
117
+ {
118
+ B2_ASSERT( ( contact->flags & b2_contactTouchingFlag ) != 0 );
119
+
120
+ int bodyIdA = contact->edges[0].bodyId;
121
+ int bodyIdB = contact->edges[1].bodyId;
122
+
123
+ b2Body* bodyA = b2BodyArray_Get( &world->bodies, bodyIdA );
124
+ b2Body* bodyB = b2BodyArray_Get( &world->bodies, bodyIdB );
125
+
126
+ B2_ASSERT( bodyA->setIndex != b2_disabledSet && bodyB->setIndex != b2_disabledSet );
127
+ B2_ASSERT( bodyA->setIndex != b2_staticSet || bodyB->setIndex != b2_staticSet );
128
+
129
+ // Wake bodyB if bodyA is awake and bodyB is sleeping
130
+ if ( bodyA->setIndex == b2_awakeSet && bodyB->setIndex >= b2_firstSleepingSet )
131
+ {
132
+ b2WakeSolverSet( world, bodyB->setIndex );
133
+ }
134
+
135
+ // Wake bodyA if bodyB is awake and bodyA is sleeping
136
+ if ( bodyB->setIndex == b2_awakeSet && bodyA->setIndex >= b2_firstSleepingSet )
137
+ {
138
+ b2WakeSolverSet( world, bodyA->setIndex );
139
+ }
140
+
141
+ int islandIdA = bodyA->islandId;
142
+ int islandIdB = bodyB->islandId;
143
+
144
+ // Static bodies have null island indices.
145
+ B2_ASSERT( bodyA->setIndex != b2_staticSet || islandIdA == B2_NULL_INDEX );
146
+ B2_ASSERT( bodyB->setIndex != b2_staticSet || islandIdB == B2_NULL_INDEX );
147
+ B2_ASSERT( islandIdA != B2_NULL_INDEX || islandIdB != B2_NULL_INDEX );
148
+
149
+ if ( islandIdA == islandIdB )
150
+ {
151
+ // Contact in same island
152
+ b2AddContactToIsland( world, islandIdA, contact );
153
+ return;
154
+ }
155
+
156
+ // Union-find root of islandA
157
+ b2Island* islandA = NULL;
158
+ if ( islandIdA != B2_NULL_INDEX )
159
+ {
160
+ islandA = b2IslandArray_Get( &world->islands, islandIdA );
161
+ int parentId = islandA->parentIsland;
162
+ while ( parentId != B2_NULL_INDEX )
163
+ {
164
+ b2Island* parent = b2IslandArray_Get( &world->islands, parentId );
165
+ if ( parent->parentIsland != B2_NULL_INDEX )
166
+ {
167
+ // path compression
168
+ islandA->parentIsland = parent->parentIsland;
169
+ }
170
+
171
+ islandA = parent;
172
+ islandIdA = parentId;
173
+ parentId = islandA->parentIsland;
174
+ }
175
+ }
176
+
177
+ // Union-find root of islandB
178
+ b2Island* islandB = NULL;
179
+ if ( islandIdB != B2_NULL_INDEX )
180
+ {
181
+ islandB = b2IslandArray_Get( &world->islands, islandIdB );
182
+ int parentId = islandB->parentIsland;
183
+ while ( islandB->parentIsland != B2_NULL_INDEX )
184
+ {
185
+ b2Island* parent = b2IslandArray_Get( &world->islands, parentId );
186
+ if ( parent->parentIsland != B2_NULL_INDEX )
187
+ {
188
+ // path compression
189
+ islandB->parentIsland = parent->parentIsland;
190
+ }
191
+
192
+ islandB = parent;
193
+ islandIdB = parentId;
194
+ parentId = islandB->parentIsland;
195
+ }
196
+ }
197
+
198
+ B2_ASSERT( islandA != NULL || islandB != NULL );
199
+
200
+ // Union-Find link island roots
201
+ if ( islandA != islandB && islandA != NULL && islandB != NULL )
202
+ {
203
+ B2_ASSERT( islandA != islandB );
204
+ B2_ASSERT( islandB->parentIsland == B2_NULL_INDEX );
205
+ islandB->parentIsland = islandIdA;
206
+ }
207
+
208
+ if ( islandA != NULL )
209
+ {
210
+ b2AddContactToIsland( world, islandIdA, contact );
211
+ }
212
+ else
213
+ {
214
+ b2AddContactToIsland( world, islandIdB, contact );
215
+ }
216
+
217
+ // todo why not merge the islands right here?
218
+ }
219
+
220
+ // This is called when a contact no longer has contact points or when a contact is destroyed.
221
+ void b2UnlinkContact( b2World* world, b2Contact* contact )
222
+ {
223
+ B2_ASSERT( contact->islandId != B2_NULL_INDEX );
224
+
225
+ // remove from island
226
+ int islandId = contact->islandId;
227
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
228
+
229
+ if ( contact->islandPrev != B2_NULL_INDEX )
230
+ {
231
+ b2Contact* prevContact = b2ContactArray_Get( &world->contacts, contact->islandPrev);
232
+ B2_ASSERT( prevContact->islandNext == contact->contactId );
233
+ prevContact->islandNext = contact->islandNext;
234
+ }
235
+
236
+ if ( contact->islandNext != B2_NULL_INDEX )
237
+ {
238
+ b2Contact* nextContact = b2ContactArray_Get( &world->contacts, contact->islandNext );
239
+ B2_ASSERT( nextContact->islandPrev == contact->contactId );
240
+ nextContact->islandPrev = contact->islandPrev;
241
+ }
242
+
243
+ if ( island->headContact == contact->contactId )
244
+ {
245
+ island->headContact = contact->islandNext;
246
+ }
247
+
248
+ if ( island->tailContact == contact->contactId )
249
+ {
250
+ island->tailContact = contact->islandPrev;
251
+ }
252
+
253
+ B2_ASSERT( island->contactCount > 0 );
254
+ island->contactCount -= 1;
255
+ island->constraintRemoveCount += 1;
256
+
257
+ contact->islandId = B2_NULL_INDEX;
258
+ contact->islandPrev = B2_NULL_INDEX;
259
+ contact->islandNext = B2_NULL_INDEX;
260
+
261
+ b2ValidateIsland( world, islandId );
262
+ }
263
+
264
+ static void b2AddJointToIsland( b2World* world, int islandId, b2Joint* joint )
265
+ {
266
+ B2_ASSERT( joint->islandId == B2_NULL_INDEX );
267
+ B2_ASSERT( joint->islandPrev == B2_NULL_INDEX );
268
+ B2_ASSERT( joint->islandNext == B2_NULL_INDEX );
269
+
270
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
271
+
272
+ if ( island->headJoint != B2_NULL_INDEX )
273
+ {
274
+ joint->islandNext = island->headJoint;
275
+ b2Joint* headJoint = b2JointArray_Get( &world->joints, island->headJoint );
276
+ headJoint->islandPrev = joint->jointId;
277
+ }
278
+
279
+ island->headJoint = joint->jointId;
280
+ if ( island->tailJoint == B2_NULL_INDEX )
281
+ {
282
+ island->tailJoint = island->headJoint;
283
+ }
284
+
285
+ island->jointCount += 1;
286
+ joint->islandId = islandId;
287
+
288
+ b2ValidateIsland( world, islandId );
289
+ }
290
+
291
+ void b2LinkJoint( b2World* world, b2Joint* joint, bool mergeIslands )
292
+ {
293
+ b2Body* bodyA = b2BodyArray_Get( &world->bodies, joint->edges[0].bodyId );
294
+ b2Body* bodyB = b2BodyArray_Get( &world->bodies, joint->edges[1].bodyId );
295
+
296
+ if ( bodyA->setIndex == b2_awakeSet && bodyB->setIndex >= b2_firstSleepingSet )
297
+ {
298
+ b2WakeSolverSet( world, bodyB->setIndex );
299
+ }
300
+ else if ( bodyB->setIndex == b2_awakeSet && bodyA->setIndex >= b2_firstSleepingSet )
301
+ {
302
+ b2WakeSolverSet( world, bodyA->setIndex );
303
+ }
304
+
305
+ int islandIdA = bodyA->islandId;
306
+ int islandIdB = bodyB->islandId;
307
+
308
+ B2_ASSERT( islandIdA != B2_NULL_INDEX || islandIdB != B2_NULL_INDEX );
309
+
310
+ if ( islandIdA == islandIdB )
311
+ {
312
+ // Joint in same island
313
+ b2AddJointToIsland( world, islandIdA, joint );
314
+ return;
315
+ }
316
+
317
+ // Union-find root of islandA
318
+ b2Island* islandA = NULL;
319
+ if ( islandIdA != B2_NULL_INDEX )
320
+ {
321
+ islandA = b2IslandArray_Get( &world->islands, islandIdA );
322
+ while ( islandA->parentIsland != B2_NULL_INDEX )
323
+ {
324
+ b2Island* parent = b2IslandArray_Get( &world->islands, islandA->parentIsland );
325
+ if ( parent->parentIsland != B2_NULL_INDEX )
326
+ {
327
+ // path compression
328
+ islandA->parentIsland = parent->parentIsland;
329
+ }
330
+
331
+ islandIdA = islandA->parentIsland;
332
+ islandA = parent;
333
+ }
334
+ }
335
+
336
+ // Union-find root of islandB
337
+ b2Island* islandB = NULL;
338
+ if ( islandIdB != B2_NULL_INDEX )
339
+ {
340
+ islandB = b2IslandArray_Get( &world->islands, islandIdB );
341
+ while ( islandB->parentIsland != B2_NULL_INDEX )
342
+ {
343
+ b2Island* parent = b2IslandArray_Get( &world->islands, islandB->parentIsland );
344
+ if ( parent->parentIsland != B2_NULL_INDEX )
345
+ {
346
+ // path compression
347
+ islandB->parentIsland = parent->parentIsland;
348
+ }
349
+
350
+ islandIdB = islandB->parentIsland;
351
+ islandB = parent;
352
+ }
353
+ }
354
+
355
+ B2_ASSERT( islandA != NULL || islandB != NULL );
356
+
357
+ // Union-Find link island roots
358
+ if ( islandA != islandB && islandA != NULL && islandB != NULL )
359
+ {
360
+ B2_ASSERT( islandA != islandB );
361
+ B2_ASSERT( islandB->parentIsland == B2_NULL_INDEX );
362
+ islandB->parentIsland = islandIdA;
363
+ }
364
+
365
+ if ( islandA != NULL )
366
+ {
367
+ b2AddJointToIsland( world, islandIdA, joint );
368
+ }
369
+ else
370
+ {
371
+ b2AddJointToIsland( world, islandIdB, joint );
372
+ }
373
+
374
+ // Joints need to have islands merged immediately when they are created
375
+ // to keep the island graph valid.
376
+ // However, when a body type is being changed the merge can be deferred until
377
+ // all joints are linked.
378
+ if (mergeIslands)
379
+ {
380
+ b2MergeAwakeIslands( world );
381
+ }
382
+ }
383
+
384
+ void b2UnlinkJoint( b2World* world, b2Joint* joint )
385
+ {
386
+ B2_ASSERT( joint->islandId != B2_NULL_INDEX );
387
+
388
+ // remove from island
389
+ int islandId = joint->islandId;
390
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
391
+
392
+ if ( joint->islandPrev != B2_NULL_INDEX )
393
+ {
394
+ b2Joint* prevJoint = b2JointArray_Get( &world->joints, joint->islandPrev );
395
+ B2_ASSERT( prevJoint->islandNext == joint->jointId );
396
+ prevJoint->islandNext = joint->islandNext;
397
+ }
398
+
399
+ if ( joint->islandNext != B2_NULL_INDEX )
400
+ {
401
+ b2Joint* nextJoint = b2JointArray_Get( &world->joints, joint->islandNext );
402
+ B2_ASSERT( nextJoint->islandPrev == joint->jointId );
403
+ nextJoint->islandPrev = joint->islandPrev;
404
+ }
405
+
406
+ if ( island->headJoint == joint->jointId )
407
+ {
408
+ island->headJoint = joint->islandNext;
409
+ }
410
+
411
+ if ( island->tailJoint == joint->jointId )
412
+ {
413
+ island->tailJoint = joint->islandPrev;
414
+ }
415
+
416
+ B2_ASSERT( island->jointCount > 0 );
417
+ island->jointCount -= 1;
418
+ island->constraintRemoveCount += 1;
419
+
420
+ joint->islandId = B2_NULL_INDEX;
421
+ joint->islandPrev = B2_NULL_INDEX;
422
+ joint->islandNext = B2_NULL_INDEX;
423
+
424
+ b2ValidateIsland( world, islandId );
425
+ }
426
+
427
+ // Merge an island into its root island.
428
+ // todo we can assume all islands are awake here
429
+ static void b2MergeIsland( b2World* world, b2Island* island )
430
+ {
431
+ B2_ASSERT( island->parentIsland != B2_NULL_INDEX );
432
+
433
+ int rootId = island->parentIsland;
434
+ b2Island* rootIsland = b2IslandArray_Get( &world->islands, rootId );
435
+ B2_ASSERT( rootIsland->parentIsland == B2_NULL_INDEX );
436
+
437
+ // remap island indices
438
+ int bodyId = island->headBody;
439
+ while ( bodyId != B2_NULL_INDEX )
440
+ {
441
+ b2Body* body = b2BodyArray_Get( &world->bodies, bodyId );
442
+ body->islandId = rootId;
443
+ bodyId = body->islandNext;
444
+ }
445
+
446
+ int contactId = island->headContact;
447
+ while ( contactId != B2_NULL_INDEX )
448
+ {
449
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
450
+ contact->islandId = rootId;
451
+ contactId = contact->islandNext;
452
+ }
453
+
454
+ int jointId = island->headJoint;
455
+ while ( jointId != B2_NULL_INDEX )
456
+ {
457
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointId );
458
+ joint->islandId = rootId;
459
+ jointId = joint->islandNext;
460
+ }
461
+
462
+ // connect body lists
463
+ B2_ASSERT( rootIsland->tailBody != B2_NULL_INDEX );
464
+ b2Body* tailBody = b2BodyArray_Get( &world->bodies, rootIsland->tailBody );
465
+ B2_ASSERT( tailBody->islandNext == B2_NULL_INDEX );
466
+ tailBody->islandNext = island->headBody;
467
+
468
+ B2_ASSERT( island->headBody != B2_NULL_INDEX );
469
+ b2Body* headBody = b2BodyArray_Get( &world->bodies, island->headBody );
470
+ B2_ASSERT( headBody->islandPrev == B2_NULL_INDEX );
471
+ headBody->islandPrev = rootIsland->tailBody;
472
+
473
+ rootIsland->tailBody = island->tailBody;
474
+ rootIsland->bodyCount += island->bodyCount;
475
+
476
+ // connect contact lists
477
+ if ( rootIsland->headContact == B2_NULL_INDEX )
478
+ {
479
+ // Root island has no contacts
480
+ B2_ASSERT( rootIsland->tailContact == B2_NULL_INDEX && rootIsland->contactCount == 0 );
481
+ rootIsland->headContact = island->headContact;
482
+ rootIsland->tailContact = island->tailContact;
483
+ rootIsland->contactCount = island->contactCount;
484
+ }
485
+ else if ( island->headContact != B2_NULL_INDEX )
486
+ {
487
+ // Both islands have contacts
488
+ B2_ASSERT( island->tailContact != B2_NULL_INDEX && island->contactCount > 0 );
489
+ B2_ASSERT( rootIsland->tailContact != B2_NULL_INDEX && rootIsland->contactCount > 0 );
490
+
491
+ b2Contact* tailContact = b2ContactArray_Get( &world->contacts, rootIsland->tailContact );
492
+ B2_ASSERT( tailContact->islandNext == B2_NULL_INDEX );
493
+ tailContact->islandNext = island->headContact;
494
+
495
+ b2Contact* headContact = b2ContactArray_Get( &world->contacts, island->headContact );
496
+ B2_ASSERT( headContact->islandPrev == B2_NULL_INDEX );
497
+ headContact->islandPrev = rootIsland->tailContact;
498
+
499
+ rootIsland->tailContact = island->tailContact;
500
+ rootIsland->contactCount += island->contactCount;
501
+ }
502
+
503
+ if ( rootIsland->headJoint == B2_NULL_INDEX )
504
+ {
505
+ // Root island has no joints
506
+ B2_ASSERT( rootIsland->tailJoint == B2_NULL_INDEX && rootIsland->jointCount == 0 );
507
+ rootIsland->headJoint = island->headJoint;
508
+ rootIsland->tailJoint = island->tailJoint;
509
+ rootIsland->jointCount = island->jointCount;
510
+ }
511
+ else if ( island->headJoint != B2_NULL_INDEX )
512
+ {
513
+ // Both islands have joints
514
+ B2_ASSERT( island->tailJoint != B2_NULL_INDEX && island->jointCount > 0 );
515
+ B2_ASSERT( rootIsland->tailJoint != B2_NULL_INDEX && rootIsland->jointCount > 0 );
516
+
517
+ b2Joint* tailJoint = b2JointArray_Get( &world->joints, rootIsland->tailJoint );
518
+ B2_ASSERT( tailJoint->islandNext == B2_NULL_INDEX );
519
+ tailJoint->islandNext = island->headJoint;
520
+
521
+ b2Joint* headJoint = b2JointArray_Get( &world->joints, island->headJoint );
522
+ B2_ASSERT( headJoint->islandPrev == B2_NULL_INDEX );
523
+ headJoint->islandPrev = rootIsland->tailJoint;
524
+
525
+ rootIsland->tailJoint = island->tailJoint;
526
+ rootIsland->jointCount += island->jointCount;
527
+ }
528
+
529
+ // Track removed constraints
530
+ rootIsland->constraintRemoveCount += island->constraintRemoveCount;
531
+
532
+ b2ValidateIsland( world, rootId );
533
+ }
534
+
535
+ // Iterate over all awake islands and merge any that need merging
536
+ // Islands that get merged into a root island will be removed from the awake island array
537
+ // and returned to the pool.
538
+ // todo this might be faster if b2IslandSim held the connectivity data
539
+ void b2MergeAwakeIslands( b2World* world )
540
+ {
541
+ b2TracyCZoneNC( merge_islands, "Merge Islands", b2_colorMediumTurquoise, true );
542
+
543
+ b2SolverSet* awakeSet = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
544
+ b2IslandSim* islandSims = awakeSet->islandSims.data;
545
+ int awakeIslandCount = awakeSet->islandSims.count;
546
+
547
+ // Step 1: Ensure every child island points to its root island. This avoids merging a child island with
548
+ // a parent island that has already been merged with a grand-parent island.
549
+ for ( int i = 0; i < awakeIslandCount; ++i )
550
+ {
551
+ int islandId = islandSims[i].islandId;
552
+
553
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
554
+
555
+ // find the root island
556
+ int rootId = islandId;
557
+ b2Island* rootIsland = island;
558
+ while ( rootIsland->parentIsland != B2_NULL_INDEX )
559
+ {
560
+ b2Island* parent = b2IslandArray_Get( &world->islands, rootIsland->parentIsland );
561
+ if ( parent->parentIsland != B2_NULL_INDEX )
562
+ {
563
+ // path compression
564
+ rootIsland->parentIsland = parent->parentIsland;
565
+ }
566
+
567
+ rootId = rootIsland->parentIsland;
568
+ rootIsland = parent;
569
+ }
570
+
571
+ if ( rootIsland != island )
572
+ {
573
+ island->parentIsland = rootId;
574
+ }
575
+ }
576
+
577
+ // Step 2: merge every awake island into its parent (which must be a root island)
578
+ // Reverse to support removal from awake array.
579
+ for ( int i = awakeIslandCount - 1; i >= 0; --i )
580
+ {
581
+ int islandId = islandSims[i].islandId;
582
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
583
+
584
+ if ( island->parentIsland == B2_NULL_INDEX )
585
+ {
586
+ continue;
587
+ }
588
+
589
+ b2MergeIsland( world, island );
590
+
591
+ // this call does a remove swap from the end of the island sim array
592
+ b2DestroyIsland( world, islandId );
593
+ }
594
+
595
+ b2ValidateConnectivity( world );
596
+
597
+ b2TracyCZoneEnd( merge_islands );
598
+ }
599
+
600
+ #define B2_CONTACT_REMOVE_THRESHOLD 1
601
+
602
+ void b2SplitIsland( b2World* world, int baseId )
603
+ {
604
+ b2Island* baseIsland = b2IslandArray_Get( &world->islands, baseId );
605
+ int setIndex = baseIsland->setIndex;
606
+
607
+ if ( setIndex != b2_awakeSet )
608
+ {
609
+ // can only split awake island
610
+ return;
611
+ }
612
+
613
+ if ( baseIsland->constraintRemoveCount == 0 )
614
+ {
615
+ // this island doesn't need to be split
616
+ return;
617
+ }
618
+
619
+ b2ValidateIsland( world, baseId );
620
+
621
+ int bodyCount = baseIsland->bodyCount;
622
+
623
+ b2Body* bodies = world->bodies.data;
624
+ b2ArenaAllocator* alloc = &world->arena;
625
+
626
+ // No lock is needed because I ensure the allocator is not used while this task is active.
627
+ int* stack = b2AllocateArenaItem( alloc, bodyCount * sizeof( int ), "island stack" );
628
+ int* bodyIds = b2AllocateArenaItem( alloc, bodyCount * sizeof( int ), "body ids" );
629
+
630
+ // Build array containing all body indices from base island. These
631
+ // serve as seed bodies for the depth first search (DFS).
632
+ int index = 0;
633
+ int nextBody = baseIsland->headBody;
634
+ while ( nextBody != B2_NULL_INDEX )
635
+ {
636
+ bodyIds[index++] = nextBody;
637
+ b2Body* body = bodies + nextBody;
638
+
639
+ // Clear visitation mark
640
+ body->isMarked = false;
641
+
642
+ nextBody = body->islandNext;
643
+ }
644
+ B2_ASSERT( index == bodyCount );
645
+
646
+ // Clear contact island flags. Only need to consider contacts
647
+ // already in the base island.
648
+ int nextContactId = baseIsland->headContact;
649
+ while ( nextContactId != B2_NULL_INDEX )
650
+ {
651
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, nextContactId );
652
+ contact->isMarked = false;
653
+ nextContactId = contact->islandNext;
654
+ }
655
+
656
+ // Clear joint island flags.
657
+ int nextJoint = baseIsland->headJoint;
658
+ while ( nextJoint != B2_NULL_INDEX )
659
+ {
660
+ b2Joint* joint = b2JointArray_Get( &world->joints, nextJoint );
661
+ joint->isMarked = false;
662
+ nextJoint = joint->islandNext;
663
+ }
664
+
665
+ // Done with the base split island.
666
+ b2DestroyIsland( world, baseId );
667
+
668
+ // Each island is found as a depth first search starting from a seed body
669
+ for ( int i = 0; i < bodyCount; ++i )
670
+ {
671
+ int seedIndex = bodyIds[i];
672
+ b2Body* seed = bodies + seedIndex;
673
+ B2_ASSERT( seed->setIndex == setIndex );
674
+
675
+ if ( seed->isMarked == true )
676
+ {
677
+ // The body has already been visited
678
+ continue;
679
+ }
680
+
681
+ int stackCount = 0;
682
+ stack[stackCount++] = seedIndex;
683
+ seed->isMarked = true;
684
+
685
+ // Create new island
686
+ // No lock needed because only a single island can split per time step. No islands are being used during the constraint
687
+ // solve. However, islands are touched during body finalization.
688
+ b2Island* island = b2CreateIsland( world, setIndex );
689
+
690
+ int islandId = island->islandId;
691
+
692
+ // Perform a depth first search (DFS) on the constraint graph.
693
+ while ( stackCount > 0 )
694
+ {
695
+ // Grab the next body off the stack and add it to the island.
696
+ int bodyId = stack[--stackCount];
697
+ b2Body* body = bodies + bodyId;
698
+ B2_ASSERT( body->setIndex == b2_awakeSet );
699
+ B2_ASSERT( body->isMarked == true );
700
+
701
+ // Add body to island
702
+ body->islandId = islandId;
703
+ if ( island->tailBody != B2_NULL_INDEX )
704
+ {
705
+ bodies[island->tailBody].islandNext = bodyId;
706
+ }
707
+ body->islandPrev = island->tailBody;
708
+ body->islandNext = B2_NULL_INDEX;
709
+ island->tailBody = bodyId;
710
+
711
+ if ( island->headBody == B2_NULL_INDEX )
712
+ {
713
+ island->headBody = bodyId;
714
+ }
715
+
716
+ island->bodyCount += 1;
717
+
718
+ // Search all contacts connected to this body.
719
+ int contactKey = body->headContactKey;
720
+ while ( contactKey != B2_NULL_INDEX )
721
+ {
722
+ int contactId = contactKey >> 1;
723
+ int edgeIndex = contactKey & 1;
724
+
725
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
726
+ B2_ASSERT( contact->contactId == contactId );
727
+
728
+ // Next key
729
+ contactKey = contact->edges[edgeIndex].nextKey;
730
+
731
+ // Has this contact already been added to this island?
732
+ if ( contact->isMarked )
733
+ {
734
+ continue;
735
+ }
736
+
737
+ // Is this contact enabled and touching?
738
+ if ( ( contact->flags & b2_contactTouchingFlag ) == 0 )
739
+ {
740
+ continue;
741
+ }
742
+
743
+ contact->isMarked = true;
744
+
745
+ int otherEdgeIndex = edgeIndex ^ 1;
746
+ int otherBodyId = contact->edges[otherEdgeIndex].bodyId;
747
+ b2Body* otherBody = bodies + otherBodyId;
748
+
749
+ // Maybe add other body to stack
750
+ if ( otherBody->isMarked == false && otherBody->setIndex != b2_staticSet )
751
+ {
752
+ B2_ASSERT( stackCount < bodyCount );
753
+ stack[stackCount++] = otherBodyId;
754
+ otherBody->isMarked = true;
755
+ }
756
+
757
+ // Add contact to island
758
+ contact->islandId = islandId;
759
+ if ( island->tailContact != B2_NULL_INDEX )
760
+ {
761
+ b2Contact* tailContact = b2ContactArray_Get( &world->contacts, island->tailContact );
762
+ tailContact->islandNext = contactId;
763
+ }
764
+ contact->islandPrev = island->tailContact;
765
+ contact->islandNext = B2_NULL_INDEX;
766
+ island->tailContact = contactId;
767
+
768
+ if ( island->headContact == B2_NULL_INDEX )
769
+ {
770
+ island->headContact = contactId;
771
+ }
772
+
773
+ island->contactCount += 1;
774
+ }
775
+
776
+ // Search all joints connect to this body.
777
+ int jointKey = body->headJointKey;
778
+ while ( jointKey != B2_NULL_INDEX )
779
+ {
780
+ int jointId = jointKey >> 1;
781
+ int edgeIndex = jointKey & 1;
782
+
783
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointId );
784
+ B2_ASSERT( joint->jointId == jointId );
785
+
786
+ // Next key
787
+ jointKey = joint->edges[edgeIndex].nextKey;
788
+
789
+ // Has this joint already been added to this island?
790
+ if ( joint->isMarked )
791
+ {
792
+ continue;
793
+ }
794
+
795
+ joint->isMarked = true;
796
+
797
+ int otherEdgeIndex = edgeIndex ^ 1;
798
+ int otherBodyId = joint->edges[otherEdgeIndex].bodyId;
799
+ b2Body* otherBody = bodies + otherBodyId;
800
+
801
+ // Don't simulate joints connected to disabled bodies.
802
+ if ( otherBody->setIndex == b2_disabledSet )
803
+ {
804
+ continue;
805
+ }
806
+
807
+ // Maybe add other body to stack
808
+ if ( otherBody->isMarked == false && otherBody->setIndex == b2_awakeSet )
809
+ {
810
+ B2_ASSERT( stackCount < bodyCount );
811
+ stack[stackCount++] = otherBodyId;
812
+ otherBody->isMarked = true;
813
+ }
814
+
815
+ // Add joint to island
816
+ joint->islandId = islandId;
817
+ if ( island->tailJoint != B2_NULL_INDEX )
818
+ {
819
+ b2Joint* tailJoint = b2JointArray_Get( &world->joints, island->tailJoint );
820
+ tailJoint->islandNext = jointId;
821
+ }
822
+ joint->islandPrev = island->tailJoint;
823
+ joint->islandNext = B2_NULL_INDEX;
824
+ island->tailJoint = jointId;
825
+
826
+ if ( island->headJoint == B2_NULL_INDEX )
827
+ {
828
+ island->headJoint = jointId;
829
+ }
830
+
831
+ island->jointCount += 1;
832
+ }
833
+ }
834
+
835
+ b2ValidateIsland( world, islandId );
836
+ }
837
+
838
+ b2FreeArenaItem( alloc, bodyIds );
839
+ b2FreeArenaItem( alloc, stack );
840
+ }
841
+
842
+ // Split an island because some contacts and/or joints have been removed.
843
+ // This is called during the constraint solve while islands are not being touched. This uses DFS and touches a lot of memory,
844
+ // so it can be quite slow.
845
+ // Note: contacts/joints connected to static bodies must belong to an island but don't affect island connectivity
846
+ // Note: static bodies are never in an island
847
+ // Note: this task interacts with some allocators without locks under the assumption that no other tasks
848
+ // are interacting with these data structures.
849
+ void b2SplitIslandTask( int startIndex, int endIndex, uint32_t threadIndex, void* context )
850
+ {
851
+ b2TracyCZoneNC( split, "Split Island", b2_colorOlive, true );
852
+
853
+ B2_UNUSED( startIndex, endIndex, threadIndex );
854
+
855
+ uint64_t ticks = b2GetTicks();
856
+ b2World* world = context;
857
+
858
+ B2_ASSERT( world->splitIslandId != B2_NULL_INDEX );
859
+
860
+ b2SplitIsland( world, world->splitIslandId );
861
+
862
+ world->profile.splitIslands += b2GetMilliseconds( ticks );
863
+ b2TracyCZoneEnd( split );
864
+ }
865
+
866
+ #if B2_VALIDATE
867
+ void b2ValidateIsland( b2World* world, int islandId )
868
+ {
869
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
870
+ B2_ASSERT( island->islandId == islandId );
871
+ B2_ASSERT( island->setIndex != B2_NULL_INDEX );
872
+ B2_ASSERT( island->headBody != B2_NULL_INDEX );
873
+
874
+ {
875
+ B2_ASSERT( island->tailBody != B2_NULL_INDEX );
876
+ B2_ASSERT( island->bodyCount > 0 );
877
+ if ( island->bodyCount > 1 )
878
+ {
879
+ B2_ASSERT( island->tailBody != island->headBody );
880
+ }
881
+ B2_ASSERT( island->bodyCount <= b2GetIdCount( &world->bodyIdPool ) );
882
+
883
+ int count = 0;
884
+ int bodyId = island->headBody;
885
+ while ( bodyId != B2_NULL_INDEX )
886
+ {
887
+ b2Body* body = b2BodyArray_Get(&world->bodies, bodyId);
888
+ B2_ASSERT( body->islandId == islandId );
889
+ B2_ASSERT( body->setIndex == island->setIndex );
890
+ count += 1;
891
+
892
+ if ( count == island->bodyCount )
893
+ {
894
+ B2_ASSERT( bodyId == island->tailBody );
895
+ }
896
+
897
+ bodyId = body->islandNext;
898
+ }
899
+ B2_ASSERT( count == island->bodyCount );
900
+ }
901
+
902
+ if ( island->headContact != B2_NULL_INDEX )
903
+ {
904
+ B2_ASSERT( island->tailContact != B2_NULL_INDEX );
905
+ B2_ASSERT( island->contactCount > 0 );
906
+ if ( island->contactCount > 1 )
907
+ {
908
+ B2_ASSERT( island->tailContact != island->headContact );
909
+ }
910
+ B2_ASSERT( island->contactCount <= b2GetIdCount( &world->contactIdPool ) );
911
+
912
+ int count = 0;
913
+ int contactId = island->headContact;
914
+ while ( contactId != B2_NULL_INDEX )
915
+ {
916
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
917
+ B2_ASSERT( contact->setIndex == island->setIndex );
918
+ B2_ASSERT( contact->islandId == islandId );
919
+ count += 1;
920
+
921
+ if ( count == island->contactCount )
922
+ {
923
+ B2_ASSERT( contactId == island->tailContact );
924
+ }
925
+
926
+ contactId = contact->islandNext;
927
+ }
928
+ B2_ASSERT( count == island->contactCount );
929
+ }
930
+ else
931
+ {
932
+ B2_ASSERT( island->tailContact == B2_NULL_INDEX );
933
+ B2_ASSERT( island->contactCount == 0 );
934
+ }
935
+
936
+ if ( island->headJoint != B2_NULL_INDEX )
937
+ {
938
+ B2_ASSERT( island->tailJoint != B2_NULL_INDEX );
939
+ B2_ASSERT( island->jointCount > 0 );
940
+ if ( island->jointCount > 1 )
941
+ {
942
+ B2_ASSERT( island->tailJoint != island->headJoint );
943
+ }
944
+ B2_ASSERT( island->jointCount <= b2GetIdCount( &world->jointIdPool ) );
945
+
946
+ int count = 0;
947
+ int jointId = island->headJoint;
948
+ while ( jointId != B2_NULL_INDEX )
949
+ {
950
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointId );
951
+ B2_ASSERT( joint->setIndex == island->setIndex );
952
+ count += 1;
953
+
954
+ if ( count == island->jointCount )
955
+ {
956
+ B2_ASSERT( jointId == island->tailJoint );
957
+ }
958
+
959
+ jointId = joint->islandNext;
960
+ }
961
+ B2_ASSERT( count == island->jointCount );
962
+ }
963
+ else
964
+ {
965
+ B2_ASSERT( island->tailJoint == B2_NULL_INDEX );
966
+ B2_ASSERT( island->jointCount == 0 );
967
+ }
968
+ }
969
+
970
+ #else
971
+
972
+ void b2ValidateIsland( b2World* world, int islandId )
973
+ {
974
+ B2_UNUSED( world );
975
+ B2_UNUSED( islandId );
976
+ }
977
+ #endif