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,3301 @@
1
+ // SPDX-FileCopyrightText: 2023 Erin Catto
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ #if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS )
5
+ #define _CRT_SECURE_NO_WARNINGS
6
+ #endif
7
+
8
+ #include "world.h"
9
+
10
+ #include "aabb.h"
11
+ #include "arena_allocator.h"
12
+ #include "array.h"
13
+ #include "bitset.h"
14
+ #include "body.h"
15
+ #include "broad_phase.h"
16
+ #include "constants.h"
17
+ #include "constraint_graph.h"
18
+ #include "contact.h"
19
+ #include "core.h"
20
+ #include "ctz.h"
21
+ #include "island.h"
22
+ #include "joint.h"
23
+ #include "sensor.h"
24
+ #include "shape.h"
25
+ #include "solver.h"
26
+ #include "solver_set.h"
27
+
28
+ #include "box2d/box2d.h"
29
+
30
+ #include <float.h>
31
+ #include <stdio.h>
32
+ #include <string.h>
33
+
34
+ _Static_assert( B2_MAX_WORLDS > 0, "must be 1 or more" );
35
+ _Static_assert( B2_MAX_WORLDS < UINT16_MAX, "B2_MAX_WORLDS limit exceeded" );
36
+ b2World b2_worlds[B2_MAX_WORLDS];
37
+
38
+ B2_ARRAY_SOURCE( b2BodyMoveEvent, b2BodyMoveEvent )
39
+ B2_ARRAY_SOURCE( b2ContactBeginTouchEvent, b2ContactBeginTouchEvent )
40
+ B2_ARRAY_SOURCE( b2ContactEndTouchEvent, b2ContactEndTouchEvent )
41
+ B2_ARRAY_SOURCE( b2ContactHitEvent, b2ContactHitEvent )
42
+ B2_ARRAY_SOURCE( b2SensorBeginTouchEvent, b2SensorBeginTouchEvent )
43
+ B2_ARRAY_SOURCE( b2SensorEndTouchEvent, b2SensorEndTouchEvent )
44
+ B2_ARRAY_SOURCE( b2TaskContext, b2TaskContext )
45
+
46
+ b2World* b2GetWorldFromId( b2WorldId id )
47
+ {
48
+ B2_ASSERT( 1 <= id.index1 && id.index1 <= B2_MAX_WORLDS );
49
+ b2World* world = b2_worlds + ( id.index1 - 1 );
50
+ B2_ASSERT( id.index1 == world->worldId + 1 );
51
+ B2_ASSERT( id.generation == world->generation );
52
+ return world;
53
+ }
54
+
55
+ b2World* b2GetWorld( int index )
56
+ {
57
+ B2_ASSERT( 0 <= index && index < B2_MAX_WORLDS );
58
+ b2World* world = b2_worlds + index;
59
+ B2_ASSERT( world->worldId == index );
60
+ return world;
61
+ }
62
+
63
+ b2World* b2GetWorldLocked( int index )
64
+ {
65
+ B2_ASSERT( 0 <= index && index < B2_MAX_WORLDS );
66
+ b2World* world = b2_worlds + index;
67
+ B2_ASSERT( world->worldId == index );
68
+ if ( world->locked )
69
+ {
70
+ B2_ASSERT( false );
71
+ return NULL;
72
+ }
73
+
74
+ return world;
75
+ }
76
+
77
+ static void* b2DefaultAddTaskFcn( b2TaskCallback* task, int count, int minRange, void* taskContext, void* userContext )
78
+ {
79
+ B2_UNUSED( minRange, userContext );
80
+ task( 0, count, 0, taskContext );
81
+ return NULL;
82
+ }
83
+
84
+ static void b2DefaultFinishTaskFcn( void* userTask, void* userContext )
85
+ {
86
+ B2_UNUSED( userTask, userContext );
87
+ }
88
+
89
+ static float b2DefaultFrictionCallback( float frictionA, int materialA, float frictionB, int materialB )
90
+ {
91
+ B2_UNUSED( materialA, materialB );
92
+ return sqrtf( frictionA * frictionB );
93
+ }
94
+
95
+ static float b2DefaultRestitutionCallback( float restitutionA, int materialA, float restitutionB, int materialB )
96
+ {
97
+ B2_UNUSED( materialA, materialB );
98
+ return b2MaxFloat( restitutionA, restitutionB );
99
+ }
100
+
101
+ b2WorldId b2CreateWorld( const b2WorldDef* def )
102
+ {
103
+ _Static_assert( B2_MAX_WORLDS < UINT16_MAX, "B2_MAX_WORLDS limit exceeded" );
104
+ B2_CHECK_DEF( def );
105
+
106
+ int worldId = B2_NULL_INDEX;
107
+ for ( int i = 0; i < B2_MAX_WORLDS; ++i )
108
+ {
109
+ if ( b2_worlds[i].inUse == false )
110
+ {
111
+ worldId = i;
112
+ break;
113
+ }
114
+ }
115
+
116
+ if ( worldId == B2_NULL_INDEX )
117
+ {
118
+ return (b2WorldId){ 0 };
119
+ }
120
+
121
+ b2InitializeContactRegisters();
122
+
123
+ b2World* world = b2_worlds + worldId;
124
+ uint16_t generation = world->generation;
125
+
126
+ *world = (b2World){ 0 };
127
+
128
+ world->worldId = (uint16_t)worldId;
129
+ world->generation = generation;
130
+ world->inUse = true;
131
+
132
+ world->arena = b2CreateArenaAllocator( 2048 );
133
+ b2CreateBroadPhase( &world->broadPhase );
134
+ b2CreateGraph( &world->constraintGraph, 16 );
135
+
136
+ // pools
137
+ world->bodyIdPool = b2CreateIdPool();
138
+ world->bodies = b2BodyArray_Create( 16 );
139
+ world->solverSets = b2SolverSetArray_Create( 8 );
140
+
141
+ // add empty static, active, and disabled body sets
142
+ world->solverSetIdPool = b2CreateIdPool();
143
+ b2SolverSet set = { 0 };
144
+
145
+ // static set
146
+ set.setIndex = b2AllocId( &world->solverSetIdPool );
147
+ b2SolverSetArray_Push( &world->solverSets, set );
148
+ B2_ASSERT( world->solverSets.data[b2_staticSet].setIndex == b2_staticSet );
149
+
150
+ // disabled set
151
+ set.setIndex = b2AllocId( &world->solverSetIdPool );
152
+ b2SolverSetArray_Push( &world->solverSets, set );
153
+ B2_ASSERT( world->solverSets.data[b2_disabledSet].setIndex == b2_disabledSet );
154
+
155
+ // awake set
156
+ set.setIndex = b2AllocId( &world->solverSetIdPool );
157
+ b2SolverSetArray_Push( &world->solverSets, set );
158
+ B2_ASSERT( world->solverSets.data[b2_awakeSet].setIndex == b2_awakeSet );
159
+
160
+ world->shapeIdPool = b2CreateIdPool();
161
+ world->shapes = b2ShapeArray_Create( 16 );
162
+
163
+ world->chainIdPool = b2CreateIdPool();
164
+ world->chainShapes = b2ChainShapeArray_Create( 4 );
165
+
166
+ world->contactIdPool = b2CreateIdPool();
167
+ world->contacts = b2ContactArray_Create( 16 );
168
+
169
+ world->jointIdPool = b2CreateIdPool();
170
+ world->joints = b2JointArray_Create( 16 );
171
+
172
+ world->islandIdPool = b2CreateIdPool();
173
+ world->islands = b2IslandArray_Create( 8 );
174
+
175
+ world->sensors = b2SensorArray_Create( 4 );
176
+
177
+ world->bodyMoveEvents = b2BodyMoveEventArray_Create( 4 );
178
+ world->sensorBeginEvents = b2SensorBeginTouchEventArray_Create( 4 );
179
+ world->sensorEndEvents[0] = b2SensorEndTouchEventArray_Create( 4 );
180
+ world->sensorEndEvents[1] = b2SensorEndTouchEventArray_Create( 4 );
181
+ world->contactBeginEvents = b2ContactBeginTouchEventArray_Create( 4 );
182
+ world->contactEndEvents[0] = b2ContactEndTouchEventArray_Create( 4 );
183
+ world->contactEndEvents[1] = b2ContactEndTouchEventArray_Create( 4 );
184
+ world->contactHitEvents = b2ContactHitEventArray_Create( 4 );
185
+ world->endEventArrayIndex = 0;
186
+
187
+ world->stepIndex = 0;
188
+ world->splitIslandId = B2_NULL_INDEX;
189
+ world->activeTaskCount = 0;
190
+ world->taskCount = 0;
191
+ world->gravity = def->gravity;
192
+ world->hitEventThreshold = def->hitEventThreshold;
193
+ world->restitutionThreshold = def->restitutionThreshold;
194
+ world->maxLinearSpeed = def->maximumLinearSpeed;
195
+ world->maxContactPushSpeed = def->maxContactPushSpeed;
196
+ world->contactHertz = def->contactHertz;
197
+ world->contactDampingRatio = def->contactDampingRatio;
198
+ world->jointHertz = def->jointHertz;
199
+ world->jointDampingRatio = def->jointDampingRatio;
200
+
201
+ if ( def->frictionCallback == NULL )
202
+ {
203
+ world->frictionCallback = b2DefaultFrictionCallback;
204
+ }
205
+ else
206
+ {
207
+ world->frictionCallback = def->frictionCallback;
208
+ }
209
+
210
+ if ( def->restitutionCallback == NULL )
211
+ {
212
+ world->restitutionCallback = b2DefaultRestitutionCallback;
213
+ }
214
+ else
215
+ {
216
+ world->restitutionCallback = def->restitutionCallback;
217
+ }
218
+
219
+ world->enableSleep = def->enableSleep;
220
+ world->locked = false;
221
+ world->enableWarmStarting = true;
222
+ world->enableContinuous = def->enableContinuous;
223
+ world->enableSpeculative = true;
224
+ world->userTreeTask = NULL;
225
+ world->userData = def->userData;
226
+
227
+ if ( def->workerCount > 0 && def->enqueueTask != NULL && def->finishTask != NULL )
228
+ {
229
+ world->workerCount = b2MinInt( def->workerCount, B2_MAX_WORKERS );
230
+ world->enqueueTaskFcn = def->enqueueTask;
231
+ world->finishTaskFcn = def->finishTask;
232
+ world->userTaskContext = def->userTaskContext;
233
+ }
234
+ else
235
+ {
236
+ world->workerCount = 1;
237
+ world->enqueueTaskFcn = b2DefaultAddTaskFcn;
238
+ world->finishTaskFcn = b2DefaultFinishTaskFcn;
239
+ world->userTaskContext = NULL;
240
+ }
241
+
242
+ world->taskContexts = b2TaskContextArray_Create( world->workerCount );
243
+ b2TaskContextArray_Resize( &world->taskContexts, world->workerCount );
244
+
245
+ world->sensorTaskContexts = b2SensorTaskContextArray_Create( world->workerCount );
246
+ b2SensorTaskContextArray_Resize( &world->sensorTaskContexts, world->workerCount );
247
+
248
+ for ( int i = 0; i < world->workerCount; ++i )
249
+ {
250
+ world->taskContexts.data[i].contactStateBitSet = b2CreateBitSet( 1024 );
251
+ world->taskContexts.data[i].enlargedSimBitSet = b2CreateBitSet( 256 );
252
+ world->taskContexts.data[i].awakeIslandBitSet = b2CreateBitSet( 256 );
253
+
254
+ world->sensorTaskContexts.data[i].eventBits = b2CreateBitSet( 128 );
255
+ }
256
+
257
+ world->debugBodySet = b2CreateBitSet( 256 );
258
+ world->debugJointSet = b2CreateBitSet( 256 );
259
+ world->debugContactSet = b2CreateBitSet( 256 );
260
+ world->debugIslandSet = b2CreateBitSet( 256 );
261
+
262
+ // add one to worldId so that 0 represents a null b2WorldId
263
+ return (b2WorldId){ (uint16_t)( worldId + 1 ), world->generation };
264
+ }
265
+
266
+ void b2DestroyWorld( b2WorldId worldId )
267
+ {
268
+ b2World* world = b2GetWorldFromId( worldId );
269
+
270
+ b2DestroyBitSet( &world->debugBodySet );
271
+ b2DestroyBitSet( &world->debugJointSet );
272
+ b2DestroyBitSet( &world->debugContactSet );
273
+ b2DestroyBitSet( &world->debugIslandSet );
274
+
275
+ for ( int i = 0; i < world->workerCount; ++i )
276
+ {
277
+ b2DestroyBitSet( &world->taskContexts.data[i].contactStateBitSet );
278
+ b2DestroyBitSet( &world->taskContexts.data[i].enlargedSimBitSet );
279
+ b2DestroyBitSet( &world->taskContexts.data[i].awakeIslandBitSet );
280
+
281
+ b2DestroyBitSet( &world->sensorTaskContexts.data[i].eventBits );
282
+ }
283
+
284
+ b2TaskContextArray_Destroy( &world->taskContexts );
285
+ b2SensorTaskContextArray_Destroy( &world->sensorTaskContexts );
286
+
287
+ b2BodyMoveEventArray_Destroy( &world->bodyMoveEvents );
288
+ b2SensorBeginTouchEventArray_Destroy( &world->sensorBeginEvents );
289
+ b2SensorEndTouchEventArray_Destroy( world->sensorEndEvents + 0 );
290
+ b2SensorEndTouchEventArray_Destroy( world->sensorEndEvents + 1 );
291
+ b2ContactBeginTouchEventArray_Destroy( &world->contactBeginEvents );
292
+ b2ContactEndTouchEventArray_Destroy( world->contactEndEvents + 0 );
293
+ b2ContactEndTouchEventArray_Destroy( world->contactEndEvents + 1 );
294
+ b2ContactHitEventArray_Destroy( &world->contactHitEvents );
295
+
296
+ int chainCapacity = world->chainShapes.count;
297
+ for ( int i = 0; i < chainCapacity; ++i )
298
+ {
299
+ b2ChainShape* chain = world->chainShapes.data + i;
300
+ if ( chain->id != B2_NULL_INDEX )
301
+ {
302
+ b2FreeChainData( chain );
303
+ }
304
+ else
305
+ {
306
+ B2_ASSERT( chain->shapeIndices == NULL );
307
+ B2_ASSERT( chain->materials == NULL );
308
+ }
309
+ }
310
+
311
+ int sensorCount = world->sensors.count;
312
+ for ( int i = 0; i < sensorCount; ++i )
313
+ {
314
+ b2ShapeRefArray_Destroy( &world->sensors.data[i].overlaps1 );
315
+ b2ShapeRefArray_Destroy( &world->sensors.data[i].overlaps2 );
316
+ }
317
+
318
+ b2SensorArray_Destroy( &world->sensors );
319
+
320
+ b2BodyArray_Destroy( &world->bodies );
321
+ b2ShapeArray_Destroy( &world->shapes );
322
+ b2ChainShapeArray_Destroy( &world->chainShapes );
323
+ b2ContactArray_Destroy( &world->contacts );
324
+ b2JointArray_Destroy( &world->joints );
325
+ b2IslandArray_Destroy( &world->islands );
326
+
327
+ // Destroy solver sets
328
+ int setCapacity = world->solverSets.count;
329
+ for ( int i = 0; i < setCapacity; ++i )
330
+ {
331
+ b2SolverSet* set = world->solverSets.data + i;
332
+ if ( set->setIndex != B2_NULL_INDEX )
333
+ {
334
+ b2DestroySolverSet( world, i );
335
+ }
336
+ }
337
+
338
+ b2SolverSetArray_Destroy( &world->solverSets );
339
+
340
+ b2DestroyGraph( &world->constraintGraph );
341
+ b2DestroyBroadPhase( &world->broadPhase );
342
+
343
+ b2DestroyIdPool( &world->bodyIdPool );
344
+ b2DestroyIdPool( &world->shapeIdPool );
345
+ b2DestroyIdPool( &world->chainIdPool );
346
+ b2DestroyIdPool( &world->contactIdPool );
347
+ b2DestroyIdPool( &world->jointIdPool );
348
+ b2DestroyIdPool( &world->islandIdPool );
349
+ b2DestroyIdPool( &world->solverSetIdPool );
350
+
351
+ b2DestroyArenaAllocator( &world->arena );
352
+
353
+ // Wipe world but preserve generation
354
+ uint16_t generation = world->generation;
355
+ *world = (b2World){ 0 };
356
+ world->worldId = 0;
357
+ world->generation = generation + 1;
358
+ }
359
+
360
+ static void b2CollideTask( int startIndex, int endIndex, uint32_t threadIndex, void* context )
361
+ {
362
+ b2TracyCZoneNC( collide_task, "Collide", b2_colorDodgerBlue, true );
363
+
364
+ b2StepContext* stepContext = context;
365
+ b2World* world = stepContext->world;
366
+ B2_ASSERT( (int)threadIndex < world->workerCount );
367
+ b2TaskContext* taskContext = world->taskContexts.data + threadIndex;
368
+ b2ContactSim** contactSims = stepContext->contacts;
369
+ b2Shape* shapes = world->shapes.data;
370
+ b2Body* bodies = world->bodies.data;
371
+
372
+ B2_ASSERT( startIndex < endIndex );
373
+
374
+ for ( int contactIndex = startIndex; contactIndex < endIndex; ++contactIndex )
375
+ {
376
+ b2ContactSim* contactSim = contactSims[contactIndex];
377
+
378
+ int contactId = contactSim->contactId;
379
+
380
+ b2Shape* shapeA = shapes + contactSim->shapeIdA;
381
+ b2Shape* shapeB = shapes + contactSim->shapeIdB;
382
+
383
+ // Do proxies still overlap?
384
+ bool overlap = b2AABB_Overlaps( shapeA->fatAABB, shapeB->fatAABB );
385
+ if ( overlap == false )
386
+ {
387
+ contactSim->simFlags |= b2_simDisjoint;
388
+ contactSim->simFlags &= ~b2_simTouchingFlag;
389
+ b2SetBit( &taskContext->contactStateBitSet, contactId );
390
+ }
391
+ else
392
+ {
393
+ bool wasTouching = ( contactSim->simFlags & b2_simTouchingFlag );
394
+
395
+ // Update contact respecting shape/body order (A,B)
396
+ b2Body* bodyA = bodies + shapeA->bodyId;
397
+ b2Body* bodyB = bodies + shapeB->bodyId;
398
+ b2BodySim* bodySimA = b2GetBodySim( world, bodyA );
399
+ b2BodySim* bodySimB = b2GetBodySim( world, bodyB );
400
+
401
+ // avoid cache misses in b2PrepareContactsTask
402
+ contactSim->bodySimIndexA = bodyA->setIndex == b2_awakeSet ? bodyA->localIndex : B2_NULL_INDEX;
403
+ contactSim->invMassA = bodySimA->invMass;
404
+ contactSim->invIA = bodySimA->invInertia;
405
+
406
+ contactSim->bodySimIndexB = bodyB->setIndex == b2_awakeSet ? bodyB->localIndex : B2_NULL_INDEX;
407
+ contactSim->invMassB = bodySimB->invMass;
408
+ contactSim->invIB = bodySimB->invInertia;
409
+
410
+ b2Transform transformA = bodySimA->transform;
411
+ b2Transform transformB = bodySimB->transform;
412
+
413
+ b2Vec2 centerOffsetA = b2RotateVector( transformA.q, bodySimA->localCenter );
414
+ b2Vec2 centerOffsetB = b2RotateVector( transformB.q, bodySimB->localCenter );
415
+
416
+ // This updates solid contacts and sensors
417
+ bool touching =
418
+ b2UpdateContact( world, contactSim, shapeA, transformA, centerOffsetA, shapeB, transformB, centerOffsetB );
419
+
420
+ // State changes that affect island connectivity. Also affects contact and sensor events.
421
+ if ( touching == true && wasTouching == false )
422
+ {
423
+ contactSim->simFlags |= b2_simStartedTouching;
424
+ b2SetBit( &taskContext->contactStateBitSet, contactId );
425
+ }
426
+ else if ( touching == false && wasTouching == true )
427
+ {
428
+ contactSim->simFlags |= b2_simStoppedTouching;
429
+ b2SetBit( &taskContext->contactStateBitSet, contactId );
430
+ }
431
+
432
+ // To make this work, the time of impact code needs to adjust the target
433
+ // distance based on the number of TOI events for a body.
434
+ // if (touching && bodySimB->isFast)
435
+ //{
436
+ // b2Manifold* manifold = &contactSim->manifold;
437
+ // int pointCount = manifold->pointCount;
438
+ // for (int i = 0; i < pointCount; ++i)
439
+ // {
440
+ // // trick the solver into pushing the fast shapes apart
441
+ // manifold->points[i].separation -= 0.25f * B2_SPECULATIVE_DISTANCE;
442
+ // }
443
+ //}
444
+ }
445
+ }
446
+
447
+ b2TracyCZoneEnd( collide_task );
448
+ }
449
+
450
+ static void b2UpdateTreesTask( int startIndex, int endIndex, uint32_t threadIndex, void* context )
451
+ {
452
+ B2_UNUSED( startIndex );
453
+ B2_UNUSED( endIndex );
454
+ B2_UNUSED( threadIndex );
455
+
456
+ b2TracyCZoneNC( tree_task, "Rebuild BVH", b2_colorFireBrick, true );
457
+
458
+ b2World* world = context;
459
+ b2BroadPhase_RebuildTrees( &world->broadPhase );
460
+
461
+ b2TracyCZoneEnd( tree_task );
462
+ }
463
+
464
+ static void b2AddNonTouchingContact( b2World* world, b2Contact* contact, b2ContactSim* contactSim )
465
+ {
466
+ B2_ASSERT( contact->setIndex == b2_awakeSet );
467
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
468
+ contact->colorIndex = B2_NULL_INDEX;
469
+ contact->localIndex = set->contactSims.count;
470
+
471
+ b2ContactSim* newContactSim = b2ContactSimArray_Add( &set->contactSims );
472
+ memcpy( newContactSim, contactSim, sizeof( b2ContactSim ) );
473
+ }
474
+
475
+ static void b2RemoveNonTouchingContact( b2World* world, int setIndex, int localIndex )
476
+ {
477
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, setIndex );
478
+ int movedIndex = b2ContactSimArray_RemoveSwap( &set->contactSims, localIndex );
479
+ if ( movedIndex != B2_NULL_INDEX )
480
+ {
481
+ b2ContactSim* movedContactSim = set->contactSims.data + localIndex;
482
+ b2Contact* movedContact = b2ContactArray_Get( &world->contacts, movedContactSim->contactId );
483
+ B2_ASSERT( movedContact->setIndex == setIndex );
484
+ B2_ASSERT( movedContact->localIndex == movedIndex );
485
+ B2_ASSERT( movedContact->colorIndex == B2_NULL_INDEX );
486
+ movedContact->localIndex = localIndex;
487
+ }
488
+ }
489
+
490
+ // Narrow-phase collision
491
+ static void b2Collide( b2StepContext* context )
492
+ {
493
+ b2World* world = context->world;
494
+
495
+ B2_ASSERT( world->workerCount > 0 );
496
+
497
+ b2TracyCZoneNC( collide, "Narrow Phase", b2_colorDodgerBlue, true );
498
+
499
+ // Task that can be done in parallel with the narrow-phase
500
+ // - rebuild the collision tree for dynamic and kinematic bodies to keep their query performance good
501
+ // todo_erin move this to start when contacts are being created
502
+ world->userTreeTask = world->enqueueTaskFcn( &b2UpdateTreesTask, 1, 1, world, world->userTaskContext );
503
+ world->taskCount += 1;
504
+ world->activeTaskCount += world->userTreeTask == NULL ? 0 : 1;
505
+
506
+ // gather contacts into a single array for easier parallel-for
507
+ int contactCount = 0;
508
+ b2GraphColor* graphColors = world->constraintGraph.colors;
509
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT; ++i )
510
+ {
511
+ contactCount += graphColors[i].contactSims.count;
512
+ }
513
+
514
+ int nonTouchingCount = world->solverSets.data[b2_awakeSet].contactSims.count;
515
+ contactCount += nonTouchingCount;
516
+
517
+ if ( contactCount == 0 )
518
+ {
519
+ b2TracyCZoneEnd( collide );
520
+ return;
521
+ }
522
+
523
+ b2ContactSim** contactSims = b2AllocateArenaItem( &world->arena, contactCount * sizeof( b2ContactSim* ), "contacts" );
524
+
525
+ int contactIndex = 0;
526
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT; ++i )
527
+ {
528
+ b2GraphColor* color = graphColors + i;
529
+ int count = color->contactSims.count;
530
+ b2ContactSim* base = color->contactSims.data;
531
+ for ( int j = 0; j < count; ++j )
532
+ {
533
+ contactSims[contactIndex] = base + j;
534
+ contactIndex += 1;
535
+ }
536
+ }
537
+
538
+ {
539
+ b2ContactSim* base = world->solverSets.data[b2_awakeSet].contactSims.data;
540
+ for ( int i = 0; i < nonTouchingCount; ++i )
541
+ {
542
+ contactSims[contactIndex] = base + i;
543
+ contactIndex += 1;
544
+ }
545
+ }
546
+
547
+ B2_ASSERT( contactIndex == contactCount );
548
+
549
+ context->contacts = contactSims;
550
+
551
+ // Contact bit set on ids because contact pointers are unstable as they move between touching and not touching.
552
+ int contactIdCapacity = b2GetIdCapacity( &world->contactIdPool );
553
+ for ( int i = 0; i < world->workerCount; ++i )
554
+ {
555
+ b2SetBitCountAndClear( &world->taskContexts.data[i].contactStateBitSet, contactIdCapacity );
556
+ }
557
+
558
+ // Task should take at least 40us on a 4GHz CPU (10K cycles)
559
+ int minRange = 64;
560
+ void* userCollideTask = world->enqueueTaskFcn( &b2CollideTask, contactCount, minRange, context, world->userTaskContext );
561
+ world->taskCount += 1;
562
+ if ( userCollideTask != NULL )
563
+ {
564
+ world->finishTaskFcn( userCollideTask, world->userTaskContext );
565
+ }
566
+
567
+ b2FreeArenaItem( &world->arena, contactSims );
568
+ context->contacts = NULL;
569
+ contactSims = NULL;
570
+
571
+ // Serially update contact state
572
+ // todo_erin bring this zone together with island merge
573
+ b2TracyCZoneNC( contact_state, "Contact State", b2_colorLightSlateGray, true );
574
+
575
+ // Bitwise OR all contact bits
576
+ b2BitSet* bitSet = &world->taskContexts.data[0].contactStateBitSet;
577
+ for ( int i = 1; i < world->workerCount; ++i )
578
+ {
579
+ b2InPlaceUnion( bitSet, &world->taskContexts.data[i].contactStateBitSet );
580
+ }
581
+
582
+ b2SolverSet* awakeSet = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
583
+
584
+ int endEventArrayIndex = world->endEventArrayIndex;
585
+
586
+ const b2Shape* shapes = world->shapes.data;
587
+ uint16_t worldId = world->worldId;
588
+
589
+ // Process contact state changes. Iterate over set bits
590
+ for ( uint32_t k = 0; k < bitSet->blockCount; ++k )
591
+ {
592
+ uint64_t bits = bitSet->bits[k];
593
+ while ( bits != 0 )
594
+ {
595
+ uint32_t ctz = b2CTZ64( bits );
596
+ int contactId = (int)( 64 * k + ctz );
597
+
598
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
599
+ B2_ASSERT( contact->setIndex == b2_awakeSet );
600
+
601
+ int colorIndex = contact->colorIndex;
602
+ int localIndex = contact->localIndex;
603
+
604
+ b2ContactSim* contactSim = NULL;
605
+ if ( colorIndex != B2_NULL_INDEX )
606
+ {
607
+ // contact lives in constraint graph
608
+ B2_ASSERT( 0 <= colorIndex && colorIndex < B2_GRAPH_COLOR_COUNT );
609
+ b2GraphColor* color = graphColors + colorIndex;
610
+ contactSim = b2ContactSimArray_Get( &color->contactSims, localIndex );
611
+ }
612
+ else
613
+ {
614
+ contactSim = b2ContactSimArray_Get( &awakeSet->contactSims, localIndex );
615
+ }
616
+
617
+ const b2Shape* shapeA = shapes + contact->shapeIdA;
618
+ const b2Shape* shapeB = shapes + contact->shapeIdB;
619
+ b2ShapeId shapeIdA = { shapeA->id + 1, worldId, shapeA->generation };
620
+ b2ShapeId shapeIdB = { shapeB->id + 1, worldId, shapeB->generation };
621
+ uint32_t flags = contact->flags;
622
+ uint32_t simFlags = contactSim->simFlags;
623
+
624
+ if ( simFlags & b2_simDisjoint )
625
+ {
626
+ // Bounding boxes no longer overlap
627
+ b2DestroyContact( world, contact, false );
628
+ contact = NULL;
629
+ contactSim = NULL;
630
+ }
631
+ else if ( simFlags & b2_simStartedTouching )
632
+ {
633
+ B2_ASSERT( contact->islandId == B2_NULL_INDEX );
634
+ // Contact is solid
635
+ if ( flags & b2_contactEnableContactEvents )
636
+ {
637
+ b2ContactBeginTouchEvent event = { shapeIdA, shapeIdB, contactSim->manifold };
638
+ b2ContactBeginTouchEventArray_Push( &world->contactBeginEvents, event );
639
+ }
640
+
641
+ B2_ASSERT( contactSim->manifold.pointCount > 0 );
642
+ B2_ASSERT( contact->setIndex == b2_awakeSet );
643
+
644
+ // Link first because this wakes colliding bodies and ensures the body sims
645
+ // are in the correct place.
646
+ contact->flags |= b2_contactTouchingFlag;
647
+ b2LinkContact( world, contact );
648
+
649
+ // Make sure these didn't change
650
+ B2_ASSERT( contact->colorIndex == B2_NULL_INDEX );
651
+ B2_ASSERT( contact->localIndex == localIndex );
652
+
653
+ // Contact sim pointer may have become orphaned due to awake set growth,
654
+ // so I just need to refresh it.
655
+ contactSim = b2ContactSimArray_Get( &awakeSet->contactSims, localIndex );
656
+
657
+ contactSim->simFlags &= ~b2_simStartedTouching;
658
+
659
+ b2AddContactToGraph( world, contactSim, contact );
660
+ b2RemoveNonTouchingContact( world, b2_awakeSet, localIndex );
661
+ contactSim = NULL;
662
+ }
663
+ else if ( simFlags & b2_simStoppedTouching )
664
+ {
665
+ contactSim->simFlags &= ~b2_simStoppedTouching;
666
+
667
+ // Contact is solid
668
+ contact->flags &= ~b2_contactTouchingFlag;
669
+
670
+ if ( contact->flags & b2_contactEnableContactEvents )
671
+ {
672
+ b2ContactEndTouchEvent event = { shapeIdA, shapeIdB };
673
+ b2ContactEndTouchEventArray_Push( world->contactEndEvents + endEventArrayIndex, event );
674
+ }
675
+
676
+ B2_ASSERT( contactSim->manifold.pointCount == 0 );
677
+
678
+ b2UnlinkContact( world, contact );
679
+ int bodyIdA = contact->edges[0].bodyId;
680
+ int bodyIdB = contact->edges[1].bodyId;
681
+
682
+ b2AddNonTouchingContact( world, contact, contactSim );
683
+ b2RemoveContactFromGraph( world, bodyIdA, bodyIdB, colorIndex, localIndex );
684
+ contact = NULL;
685
+ contactSim = NULL;
686
+ }
687
+
688
+ // Clear the smallest set bit
689
+ bits = bits & ( bits - 1 );
690
+ }
691
+ }
692
+
693
+ b2ValidateSolverSets( world );
694
+ b2ValidateContacts( world );
695
+
696
+ b2TracyCZoneEnd( contact_state );
697
+ b2TracyCZoneEnd( collide );
698
+ }
699
+
700
+ void b2World_Step( b2WorldId worldId, float timeStep, int subStepCount )
701
+ {
702
+ B2_ASSERT( b2IsValidFloat( timeStep ) );
703
+ B2_ASSERT( 0 < subStepCount );
704
+
705
+ b2World* world = b2GetWorldFromId( worldId );
706
+ B2_ASSERT( world->locked == false );
707
+ if ( world->locked )
708
+ {
709
+ return;
710
+ }
711
+
712
+ // Prepare to capture events
713
+ // Ensure user does not access stale data if there is an early return
714
+ b2BodyMoveEventArray_Clear( &world->bodyMoveEvents );
715
+ b2SensorBeginTouchEventArray_Clear( &world->sensorBeginEvents );
716
+ b2ContactBeginTouchEventArray_Clear( &world->contactBeginEvents );
717
+ b2ContactHitEventArray_Clear( &world->contactHitEvents );
718
+
719
+ world->profile = (b2Profile){ 0 };
720
+
721
+ if ( timeStep == 0.0f )
722
+ {
723
+ // Swap end event array buffers
724
+ world->endEventArrayIndex = 1 - world->endEventArrayIndex;
725
+ b2SensorEndTouchEventArray_Clear( world->sensorEndEvents + world->endEventArrayIndex );
726
+ b2ContactEndTouchEventArray_Clear( world->contactEndEvents + world->endEventArrayIndex );
727
+
728
+ // todo_erin would be useful to still process collision while paused
729
+ return;
730
+ }
731
+
732
+ b2TracyCZoneNC( world_step, "Step", b2_colorBox2DGreen, true );
733
+
734
+ world->locked = true;
735
+ world->activeTaskCount = 0;
736
+ world->taskCount = 0;
737
+
738
+ uint64_t stepTicks = b2GetTicks();
739
+
740
+ // Update collision pairs and create contacts
741
+ {
742
+ uint64_t pairTicks = b2GetTicks();
743
+ b2UpdateBroadPhasePairs( world );
744
+ world->profile.pairs = b2GetMilliseconds( pairTicks );
745
+ }
746
+
747
+ b2StepContext context = { 0 };
748
+ context.world = world;
749
+ context.dt = timeStep;
750
+ context.subStepCount = b2MaxInt( 1, subStepCount );
751
+
752
+ if ( timeStep > 0.0f )
753
+ {
754
+ context.inv_dt = 1.0f / timeStep;
755
+ context.h = timeStep / context.subStepCount;
756
+ context.inv_h = context.subStepCount * context.inv_dt;
757
+ }
758
+ else
759
+ {
760
+ context.inv_dt = 0.0f;
761
+ context.h = 0.0f;
762
+ context.inv_h = 0.0f;
763
+ }
764
+
765
+ world->inv_h = context.inv_h;
766
+
767
+ // Hertz values get reduced for large time steps
768
+ float contactHertz = b2MinFloat( world->contactHertz, 0.25f * context.inv_h );
769
+ float jointHertz = b2MinFloat( world->jointHertz, 0.125f * context.inv_h );
770
+
771
+ context.contactSoftness = b2MakeSoft( contactHertz, world->contactDampingRatio, context.h );
772
+ context.staticSoftness = b2MakeSoft( 2.0f * contactHertz, world->contactDampingRatio, context.h );
773
+ context.jointSoftness = b2MakeSoft( jointHertz, world->jointDampingRatio, context.h );
774
+
775
+ context.restitutionThreshold = world->restitutionThreshold;
776
+ context.maxLinearVelocity = world->maxLinearSpeed;
777
+ context.enableWarmStarting = world->enableWarmStarting;
778
+
779
+ // Update contacts
780
+ {
781
+ uint64_t collideTicks = b2GetTicks();
782
+ b2Collide( &context );
783
+ world->profile.collide = b2GetMilliseconds( collideTicks );
784
+ }
785
+
786
+ // Integrate velocities, solve velocity constraints, and integrate positions.
787
+ if ( context.dt > 0.0f )
788
+ {
789
+ uint64_t solveTicks = b2GetTicks();
790
+ b2Solve( world, &context );
791
+ world->profile.solve = b2GetMilliseconds( solveTicks );
792
+ }
793
+
794
+ // Update sensors
795
+ {
796
+ uint64_t sensorTicks = b2GetTicks();
797
+ b2OverlapSensors( world );
798
+ world->profile.sensors = b2GetMilliseconds( sensorTicks );
799
+ }
800
+
801
+ world->profile.step = b2GetMilliseconds( stepTicks );
802
+
803
+ B2_ASSERT( b2GetArenaAllocation( &world->arena ) == 0 );
804
+
805
+ // Ensure stack is large enough
806
+ b2GrowArena( &world->arena );
807
+
808
+ // Make sure all tasks that were started were also finished
809
+ B2_ASSERT( world->activeTaskCount == 0 );
810
+
811
+ b2TracyCZoneEnd( world_step );
812
+
813
+ // Swap end event array buffers
814
+ world->endEventArrayIndex = 1 - world->endEventArrayIndex;
815
+ b2SensorEndTouchEventArray_Clear( world->sensorEndEvents + world->endEventArrayIndex );
816
+ b2ContactEndTouchEventArray_Clear( world->contactEndEvents + world->endEventArrayIndex );
817
+ world->locked = false;
818
+ }
819
+
820
+ static void b2DrawShape( b2DebugDraw* draw, b2Shape* shape, b2Transform xf, b2HexColor color )
821
+ {
822
+ switch ( shape->type )
823
+ {
824
+ case b2_capsuleShape:
825
+ {
826
+ b2Capsule* capsule = &shape->capsule;
827
+ b2Vec2 p1 = b2TransformPoint( xf, capsule->center1 );
828
+ b2Vec2 p2 = b2TransformPoint( xf, capsule->center2 );
829
+ draw->DrawSolidCapsuleFcn( p1, p2, capsule->radius, color, draw->context );
830
+ }
831
+ break;
832
+
833
+ case b2_circleShape:
834
+ {
835
+ b2Circle* circle = &shape->circle;
836
+ xf.p = b2TransformPoint( xf, circle->center );
837
+ draw->DrawSolidCircleFcn( xf, circle->radius, color, draw->context );
838
+ }
839
+ break;
840
+
841
+ case b2_polygonShape:
842
+ {
843
+ b2Polygon* poly = &shape->polygon;
844
+ draw->DrawSolidPolygonFcn( xf, poly->vertices, poly->count, poly->radius, color, draw->context );
845
+ }
846
+ break;
847
+
848
+ case b2_segmentShape:
849
+ {
850
+ b2Segment* segment = &shape->segment;
851
+ b2Vec2 p1 = b2TransformPoint( xf, segment->point1 );
852
+ b2Vec2 p2 = b2TransformPoint( xf, segment->point2 );
853
+ draw->DrawSegmentFcn( p1, p2, color, draw->context );
854
+ }
855
+ break;
856
+
857
+ case b2_chainSegmentShape:
858
+ {
859
+ b2Segment* segment = &shape->chainSegment.segment;
860
+ b2Vec2 p1 = b2TransformPoint( xf, segment->point1 );
861
+ b2Vec2 p2 = b2TransformPoint( xf, segment->point2 );
862
+ draw->DrawSegmentFcn( p1, p2, color, draw->context );
863
+ draw->DrawPointFcn( p2, 4.0f, color, draw->context );
864
+ draw->DrawSegmentFcn( p1, b2Lerp( p1, p2, 0.1f ), b2_colorPaleGreen, draw->context );
865
+ }
866
+ break;
867
+
868
+ default:
869
+ break;
870
+ }
871
+ }
872
+
873
+ struct DrawContext
874
+ {
875
+ b2World* world;
876
+ b2DebugDraw* draw;
877
+ };
878
+
879
+ static bool DrawQueryCallback( int proxyId, uint64_t userData, void* context )
880
+ {
881
+ B2_UNUSED( proxyId );
882
+
883
+ int shapeId = (int)userData;
884
+
885
+ struct DrawContext* drawContext = context;
886
+ b2World* world = drawContext->world;
887
+ b2DebugDraw* draw = drawContext->draw;
888
+
889
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
890
+ B2_ASSERT( shape->id == shapeId );
891
+
892
+ b2SetBit( &world->debugBodySet, shape->bodyId );
893
+
894
+ if ( draw->drawShapes )
895
+ {
896
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
897
+ b2BodySim* bodySim = b2GetBodySim( world, body );
898
+
899
+ b2HexColor color;
900
+
901
+ if ( shape->customColor != 0 )
902
+ {
903
+ color = shape->customColor;
904
+ }
905
+ else if ( body->type == b2_dynamicBody && body->mass == 0.0f )
906
+ {
907
+ // Bad body
908
+ color = b2_colorRed;
909
+ }
910
+ else if ( body->setIndex == b2_disabledSet )
911
+ {
912
+ color = b2_colorSlateGray;
913
+ }
914
+ else if ( shape->sensorIndex != B2_NULL_INDEX )
915
+ {
916
+ color = b2_colorWheat;
917
+ }
918
+ else if ( bodySim->isBullet && body->setIndex == b2_awakeSet )
919
+ {
920
+ color = b2_colorTurquoise;
921
+ }
922
+ else if ( body->isSpeedCapped )
923
+ {
924
+ color = b2_colorYellow;
925
+ }
926
+ else if ( bodySim->isFast )
927
+ {
928
+ color = b2_colorSalmon;
929
+ }
930
+ else if ( body->type == b2_staticBody )
931
+ {
932
+ color = b2_colorPaleGreen;
933
+ }
934
+ else if ( body->type == b2_kinematicBody )
935
+ {
936
+ color = b2_colorRoyalBlue;
937
+ }
938
+ else if ( body->setIndex == b2_awakeSet )
939
+ {
940
+ color = b2_colorPink;
941
+ }
942
+ else
943
+ {
944
+ color = b2_colorGray;
945
+ }
946
+
947
+ b2DrawShape( draw, shape, bodySim->transform, color );
948
+ }
949
+
950
+ if ( draw->drawBounds )
951
+ {
952
+ b2AABB aabb = shape->fatAABB;
953
+
954
+ b2Vec2 vs[4] = { { aabb.lowerBound.x, aabb.lowerBound.y },
955
+ { aabb.upperBound.x, aabb.lowerBound.y },
956
+ { aabb.upperBound.x, aabb.upperBound.y },
957
+ { aabb.lowerBound.x, aabb.upperBound.y } };
958
+
959
+ draw->DrawPolygonFcn( vs, 4, b2_colorGold, draw->context );
960
+ }
961
+
962
+ return true;
963
+ }
964
+
965
+ // todo this has varying order for moving shapes, causing flicker when overlapping shapes are moving
966
+ // solution: display order by shape id modulus 3, keep 3 buckets in GLSolid* and flush in 3 passes.
967
+ static void b2DrawWithBounds( b2World* world, b2DebugDraw* draw )
968
+ {
969
+ B2_ASSERT( b2IsValidAABB( draw->drawingBounds ) );
970
+
971
+ const float k_impulseScale = 1.0f;
972
+ const float k_axisScale = 0.3f;
973
+ b2HexColor speculativeColor = b2_colorGainsboro;
974
+ b2HexColor addColor = b2_colorGreen;
975
+ b2HexColor persistColor = b2_colorBlue;
976
+ b2HexColor normalColor = b2_colorDimGray;
977
+ b2HexColor impulseColor = b2_colorMagenta;
978
+ b2HexColor frictionColor = b2_colorYellow;
979
+
980
+ b2HexColor graphColors[B2_GRAPH_COLOR_COUNT] = { b2_colorRed, b2_colorOrange, b2_colorYellow, b2_colorGreen,
981
+ b2_colorCyan, b2_colorBlue, b2_colorViolet, b2_colorPink,
982
+ b2_colorChocolate, b2_colorGoldenRod, b2_colorCoral, b2_colorBlack };
983
+
984
+ int bodyCapacity = b2GetIdCapacity( &world->bodyIdPool );
985
+ b2SetBitCountAndClear( &world->debugBodySet, bodyCapacity );
986
+
987
+ int jointCapacity = b2GetIdCapacity( &world->jointIdPool );
988
+ b2SetBitCountAndClear( &world->debugJointSet, jointCapacity );
989
+
990
+ int contactCapacity = b2GetIdCapacity( &world->contactIdPool );
991
+ b2SetBitCountAndClear( &world->debugContactSet, contactCapacity );
992
+
993
+ struct DrawContext drawContext = { world, draw };
994
+
995
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
996
+ {
997
+ b2DynamicTree_Query( world->broadPhase.trees + i, draw->drawingBounds, B2_DEFAULT_MASK_BITS, DrawQueryCallback,
998
+ &drawContext );
999
+ }
1000
+
1001
+ uint32_t wordCount = world->debugBodySet.blockCount;
1002
+ uint64_t* bits = world->debugBodySet.bits;
1003
+ for ( uint32_t k = 0; k < wordCount; ++k )
1004
+ {
1005
+ uint64_t word = bits[k];
1006
+ while ( word != 0 )
1007
+ {
1008
+ uint32_t ctz = b2CTZ64( word );
1009
+ uint32_t bodyId = 64 * k + ctz;
1010
+
1011
+ b2Body* body = b2BodyArray_Get( &world->bodies, bodyId );
1012
+
1013
+ if ( draw->drawBodyNames && body->name[0] != 0 )
1014
+ {
1015
+ b2Vec2 offset = { 0.1f, 0.1f };
1016
+ b2BodySim* bodySim = b2GetBodySim( world, body );
1017
+
1018
+ b2Transform transform = { bodySim->center, bodySim->transform.q };
1019
+ b2Vec2 p = b2TransformPoint( transform, offset );
1020
+ draw->DrawStringFcn( p, body->name, b2_colorBlueViolet, draw->context );
1021
+ }
1022
+
1023
+ if ( draw->drawMass && body->type == b2_dynamicBody )
1024
+ {
1025
+ b2Vec2 offset = { 0.1f, 0.1f };
1026
+ b2BodySim* bodySim = b2GetBodySim( world, body );
1027
+
1028
+ b2Transform transform = { bodySim->center, bodySim->transform.q };
1029
+ draw->DrawTransformFcn( transform, draw->context );
1030
+
1031
+ b2Vec2 p = b2TransformPoint( transform, offset );
1032
+
1033
+ char buffer[32];
1034
+ snprintf( buffer, 32, " %.2f", body->mass );
1035
+ draw->DrawStringFcn( p, buffer, b2_colorWhite, draw->context );
1036
+ }
1037
+
1038
+ if ( draw->drawJoints )
1039
+ {
1040
+ int jointKey = body->headJointKey;
1041
+ while ( jointKey != B2_NULL_INDEX )
1042
+ {
1043
+ int jointId = jointKey >> 1;
1044
+ int edgeIndex = jointKey & 1;
1045
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointId );
1046
+
1047
+ // avoid double draw
1048
+ if ( b2GetBit( &world->debugJointSet, jointId ) == false )
1049
+ {
1050
+ b2DrawJoint( draw, world, joint );
1051
+ b2SetBit( &world->debugJointSet, jointId );
1052
+ }
1053
+ else
1054
+ {
1055
+ // todo testing
1056
+ edgeIndex += 0;
1057
+ }
1058
+
1059
+ jointKey = joint->edges[edgeIndex].nextKey;
1060
+ }
1061
+ }
1062
+
1063
+ const float linearSlop = B2_LINEAR_SLOP;
1064
+ if ( draw->drawContacts && body->type == b2_dynamicBody && body->setIndex == b2_awakeSet )
1065
+ {
1066
+ int contactKey = body->headContactKey;
1067
+ while ( contactKey != B2_NULL_INDEX )
1068
+ {
1069
+ int contactId = contactKey >> 1;
1070
+ int edgeIndex = contactKey & 1;
1071
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
1072
+ contactKey = contact->edges[edgeIndex].nextKey;
1073
+
1074
+ if ( contact->setIndex != b2_awakeSet || contact->colorIndex == B2_NULL_INDEX )
1075
+ {
1076
+ continue;
1077
+ }
1078
+
1079
+ // avoid double draw
1080
+ if ( b2GetBit( &world->debugContactSet, contactId ) == false )
1081
+ {
1082
+ B2_ASSERT( 0 <= contact->colorIndex && contact->colorIndex < B2_GRAPH_COLOR_COUNT );
1083
+
1084
+ b2GraphColor* gc = world->constraintGraph.colors + contact->colorIndex;
1085
+ b2ContactSim* contactSim = b2ContactSimArray_Get( &gc->contactSims, contact->localIndex );
1086
+ int pointCount = contactSim->manifold.pointCount;
1087
+ b2Vec2 normal = contactSim->manifold.normal;
1088
+ char buffer[32];
1089
+
1090
+ for ( int j = 0; j < pointCount; ++j )
1091
+ {
1092
+ b2ManifoldPoint* point = contactSim->manifold.points + j;
1093
+
1094
+ if ( draw->drawGraphColors )
1095
+ {
1096
+ // graph color
1097
+ float pointSize = contact->colorIndex == B2_OVERFLOW_INDEX ? 7.5f : 5.0f;
1098
+ draw->DrawPointFcn( point->point, pointSize, graphColors[contact->colorIndex], draw->context );
1099
+ // g_draw.DrawString(point->position, "%d", point->color);
1100
+ }
1101
+ else if ( point->separation > linearSlop )
1102
+ {
1103
+ // Speculative
1104
+ draw->DrawPointFcn( point->point, 5.0f, speculativeColor, draw->context );
1105
+ }
1106
+ else if ( point->persisted == false )
1107
+ {
1108
+ // Add
1109
+ draw->DrawPointFcn( point->point, 10.0f, addColor, draw->context );
1110
+ }
1111
+ else if ( point->persisted == true )
1112
+ {
1113
+ // Persist
1114
+ draw->DrawPointFcn( point->point, 5.0f, persistColor, draw->context );
1115
+ }
1116
+
1117
+ if ( draw->drawContactNormals )
1118
+ {
1119
+ b2Vec2 p1 = point->point;
1120
+ b2Vec2 p2 = b2MulAdd( p1, k_axisScale, normal );
1121
+ draw->DrawSegmentFcn( p1, p2, normalColor, draw->context );
1122
+ }
1123
+ else if ( draw->drawContactImpulses )
1124
+ {
1125
+ b2Vec2 p1 = point->point;
1126
+ b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->normalImpulse, normal );
1127
+ draw->DrawSegmentFcn( p1, p2, impulseColor, draw->context );
1128
+ snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.1f", 1000.0f * point->normalImpulse );
1129
+ draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context );
1130
+ }
1131
+
1132
+ if ( draw->drawContactFeatures )
1133
+ {
1134
+ snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%d", point->id );
1135
+ draw->DrawStringFcn( point->point, buffer, b2_colorOrange, draw->context );
1136
+ }
1137
+
1138
+ if ( draw->drawFrictionImpulses )
1139
+ {
1140
+ b2Vec2 tangent = b2RightPerp( normal );
1141
+ b2Vec2 p1 = point->point;
1142
+ b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->tangentImpulse, tangent );
1143
+ draw->DrawSegmentFcn( p1, p2, frictionColor, draw->context );
1144
+ snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.1f", 1000.0f * point->tangentImpulse );
1145
+ draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context );
1146
+ }
1147
+ }
1148
+
1149
+ b2SetBit( &world->debugContactSet, contactId );
1150
+ }
1151
+ else
1152
+ {
1153
+ // todo testing
1154
+ edgeIndex += 0;
1155
+ }
1156
+
1157
+ contactKey = contact->edges[edgeIndex].nextKey;
1158
+ }
1159
+ }
1160
+
1161
+ // Clear the smallest set bit
1162
+ word = word & ( word - 1 );
1163
+ }
1164
+ }
1165
+ }
1166
+
1167
+ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw )
1168
+ {
1169
+ b2World* world = b2GetWorldFromId( worldId );
1170
+ B2_ASSERT( world->locked == false );
1171
+ if ( world->locked )
1172
+ {
1173
+ return;
1174
+ }
1175
+
1176
+ // todo it seems bounds drawing is fast enough for regular usage
1177
+ if ( draw->useDrawingBounds )
1178
+ {
1179
+ b2DrawWithBounds( world, draw );
1180
+ return;
1181
+ }
1182
+
1183
+ if ( draw->drawShapes )
1184
+ {
1185
+ int setCount = world->solverSets.count;
1186
+ for ( int setIndex = 0; setIndex < setCount; ++setIndex )
1187
+ {
1188
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, setIndex );
1189
+ int bodyCount = set->bodySims.count;
1190
+ for ( int bodyIndex = 0; bodyIndex < bodyCount; ++bodyIndex )
1191
+ {
1192
+ b2BodySim* bodySim = set->bodySims.data + bodyIndex;
1193
+ b2Body* body = b2BodyArray_Get( &world->bodies, bodySim->bodyId );
1194
+ B2_ASSERT( body->setIndex == setIndex );
1195
+
1196
+ b2Transform xf = bodySim->transform;
1197
+ int shapeId = body->headShapeId;
1198
+ while ( shapeId != B2_NULL_INDEX )
1199
+ {
1200
+ b2Shape* shape = world->shapes.data + shapeId;
1201
+ b2HexColor color;
1202
+
1203
+ if ( shape->customColor != 0 )
1204
+ {
1205
+ color = shape->customColor;
1206
+ }
1207
+ else if ( body->type == b2_dynamicBody && body->mass == 0.0f )
1208
+ {
1209
+ // Bad body
1210
+ color = b2_colorRed;
1211
+ }
1212
+ else if ( body->setIndex == b2_disabledSet )
1213
+ {
1214
+ color = b2_colorSlateGray;
1215
+ }
1216
+ else if ( shape->sensorIndex != B2_NULL_INDEX )
1217
+ {
1218
+ color = b2_colorWheat;
1219
+ }
1220
+ else if ( bodySim->isBullet && body->setIndex == b2_awakeSet )
1221
+ {
1222
+ color = b2_colorTurquoise;
1223
+ }
1224
+ else if ( body->isSpeedCapped )
1225
+ {
1226
+ color = b2_colorYellow;
1227
+ }
1228
+ else if ( bodySim->isFast )
1229
+ {
1230
+ color = b2_colorSalmon;
1231
+ }
1232
+ else if ( body->type == b2_staticBody )
1233
+ {
1234
+ color = b2_colorPaleGreen;
1235
+ }
1236
+ else if ( body->type == b2_kinematicBody )
1237
+ {
1238
+ color = b2_colorRoyalBlue;
1239
+ }
1240
+ else if ( body->setIndex == b2_awakeSet )
1241
+ {
1242
+ color = b2_colorPink;
1243
+ }
1244
+ else
1245
+ {
1246
+ color = b2_colorGray;
1247
+ }
1248
+
1249
+ b2DrawShape( draw, shape, xf, color );
1250
+ shapeId = shape->nextShapeId;
1251
+ }
1252
+ }
1253
+ }
1254
+ }
1255
+
1256
+ if ( draw->drawJoints )
1257
+ {
1258
+ int count = world->joints.count;
1259
+ for ( int i = 0; i < count; ++i )
1260
+ {
1261
+ b2Joint* joint = world->joints.data + i;
1262
+ if ( joint->setIndex == B2_NULL_INDEX )
1263
+ {
1264
+ continue;
1265
+ }
1266
+
1267
+ b2DrawJoint( draw, world, joint );
1268
+ }
1269
+ }
1270
+
1271
+ if ( draw->drawBounds )
1272
+ {
1273
+ b2HexColor color = b2_colorGold;
1274
+
1275
+ int setCount = world->solverSets.count;
1276
+ for ( int setIndex = 0; setIndex < setCount; ++setIndex )
1277
+ {
1278
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, setIndex );
1279
+ int bodyCount = set->bodySims.count;
1280
+ for ( int bodyIndex = 0; bodyIndex < bodyCount; ++bodyIndex )
1281
+ {
1282
+ b2BodySim* bodySim = set->bodySims.data + bodyIndex;
1283
+
1284
+ char buffer[32];
1285
+ snprintf( buffer, 32, "%d", bodySim->bodyId );
1286
+ draw->DrawStringFcn( bodySim->center, buffer, b2_colorWhite, draw->context );
1287
+
1288
+ b2Body* body = b2BodyArray_Get( &world->bodies, bodySim->bodyId );
1289
+ B2_ASSERT( body->setIndex == setIndex );
1290
+
1291
+ int shapeId = body->headShapeId;
1292
+ while ( shapeId != B2_NULL_INDEX )
1293
+ {
1294
+ b2Shape* shape = world->shapes.data + shapeId;
1295
+ b2AABB aabb = shape->fatAABB;
1296
+
1297
+ b2Vec2 vs[4] = { { aabb.lowerBound.x, aabb.lowerBound.y },
1298
+ { aabb.upperBound.x, aabb.lowerBound.y },
1299
+ { aabb.upperBound.x, aabb.upperBound.y },
1300
+ { aabb.lowerBound.x, aabb.upperBound.y } };
1301
+
1302
+ draw->DrawPolygonFcn( vs, 4, color, draw->context );
1303
+
1304
+ shapeId = shape->nextShapeId;
1305
+ }
1306
+ }
1307
+ }
1308
+ }
1309
+
1310
+ if ( draw->drawBodyNames )
1311
+ {
1312
+ b2Vec2 offset = { 0.05f, 0.05f };
1313
+ int count = world->bodies.count;
1314
+ for ( int i = 0; i < count; ++i )
1315
+ {
1316
+ b2Body* body = world->bodies.data + i;
1317
+ if ( body->setIndex == B2_NULL_INDEX )
1318
+ {
1319
+ continue;
1320
+ }
1321
+
1322
+ if ( body->name[0] == 0 )
1323
+ {
1324
+ continue;
1325
+ }
1326
+
1327
+ b2BodySim* bodySim = b2GetBodySim( world, body );
1328
+
1329
+ b2Transform transform = { bodySim->center, bodySim->transform.q };
1330
+ b2Vec2 p = b2TransformPoint( transform, offset );
1331
+ draw->DrawStringFcn( p, body->name, b2_colorBlueViolet, draw->context );
1332
+ }
1333
+ }
1334
+
1335
+ if ( draw->drawMass )
1336
+ {
1337
+ b2Vec2 offset = { 0.1f, 0.1f };
1338
+ int setCount = world->solverSets.count;
1339
+ for ( int setIndex = 0; setIndex < setCount; ++setIndex )
1340
+ {
1341
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, setIndex );
1342
+ int bodyCount = set->bodySims.count;
1343
+ for ( int bodyIndex = 0; bodyIndex < bodyCount; ++bodyIndex )
1344
+ {
1345
+ b2BodySim* bodySim = set->bodySims.data + bodyIndex;
1346
+
1347
+ b2Transform transform = { bodySim->center, bodySim->transform.q };
1348
+ draw->DrawTransformFcn( transform, draw->context );
1349
+
1350
+ b2Vec2 p = b2TransformPoint( transform, offset );
1351
+
1352
+ char buffer[32];
1353
+ float mass = bodySim->invMass > 0.0f ? 1.0f / bodySim->invMass : 0.0f;
1354
+ snprintf( buffer, 32, " %.2f", mass );
1355
+ draw->DrawStringFcn( p, buffer, b2_colorWhite, draw->context );
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ if ( draw->drawContacts )
1361
+ {
1362
+ const float k_impulseScale = 1.0f;
1363
+ const float k_axisScale = 0.3f;
1364
+ const float linearSlop = B2_LINEAR_SLOP;
1365
+
1366
+ b2HexColor speculativeColor = b2_colorLightGray;
1367
+ b2HexColor addColor = b2_colorGreen;
1368
+ b2HexColor persistColor = b2_colorBlue;
1369
+ b2HexColor normalColor = b2_colorDimGray;
1370
+ b2HexColor impulseColor = b2_colorMagenta;
1371
+ b2HexColor frictionColor = b2_colorYellow;
1372
+
1373
+ b2HexColor colors[B2_GRAPH_COLOR_COUNT] = { b2_colorRed, b2_colorOrange, b2_colorYellow, b2_colorGreen,
1374
+ b2_colorCyan, b2_colorBlue, b2_colorViolet, b2_colorPink,
1375
+ b2_colorChocolate, b2_colorGoldenRod, b2_colorCoral, b2_colorBlack };
1376
+
1377
+ for ( int colorIndex = 0; colorIndex < B2_GRAPH_COLOR_COUNT; ++colorIndex )
1378
+ {
1379
+ b2GraphColor* graphColor = world->constraintGraph.colors + colorIndex;
1380
+
1381
+ int contactCount = graphColor->contactSims.count;
1382
+ for ( int contactIndex = 0; contactIndex < contactCount; ++contactIndex )
1383
+ {
1384
+ b2ContactSim* contact = graphColor->contactSims.data + contactIndex;
1385
+ int pointCount = contact->manifold.pointCount;
1386
+ b2Vec2 normal = contact->manifold.normal;
1387
+ char buffer[32];
1388
+
1389
+ for ( int j = 0; j < pointCount; ++j )
1390
+ {
1391
+ b2ManifoldPoint* point = contact->manifold.points + j;
1392
+
1393
+ if ( draw->drawGraphColors && 0 <= colorIndex && colorIndex <= B2_GRAPH_COLOR_COUNT )
1394
+ {
1395
+ // graph color
1396
+ float pointSize = colorIndex == B2_OVERFLOW_INDEX ? 7.5f : 5.0f;
1397
+ draw->DrawPointFcn( point->point, pointSize, colors[colorIndex], draw->context );
1398
+ // g_draw.DrawString(point->position, "%d", point->color);
1399
+ }
1400
+ else if ( point->separation > linearSlop )
1401
+ {
1402
+ // Speculative
1403
+ draw->DrawPointFcn( point->point, 5.0f, speculativeColor, draw->context );
1404
+ }
1405
+ else if ( point->persisted == false )
1406
+ {
1407
+ // Add
1408
+ draw->DrawPointFcn( point->point, 10.0f, addColor, draw->context );
1409
+ }
1410
+ else if ( point->persisted == true )
1411
+ {
1412
+ // Persist
1413
+ draw->DrawPointFcn( point->point, 5.0f, persistColor, draw->context );
1414
+ }
1415
+
1416
+ if ( draw->drawContactNormals )
1417
+ {
1418
+ b2Vec2 p1 = point->point;
1419
+ b2Vec2 p2 = b2MulAdd( p1, k_axisScale, normal );
1420
+ draw->DrawSegmentFcn( p1, p2, normalColor, draw->context );
1421
+ }
1422
+ else if ( draw->drawContactImpulses )
1423
+ {
1424
+ b2Vec2 p1 = point->point;
1425
+ b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->normalImpulse, normal );
1426
+ draw->DrawSegmentFcn( p1, p2, impulseColor, draw->context );
1427
+ snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.2f", 1000.0f * point->normalImpulse );
1428
+ draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context );
1429
+ }
1430
+
1431
+ if ( draw->drawContactFeatures )
1432
+ {
1433
+ snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%d", point->id );
1434
+ draw->DrawStringFcn( point->point, buffer, b2_colorOrange, draw->context );
1435
+ }
1436
+
1437
+ if ( draw->drawFrictionImpulses )
1438
+ {
1439
+ b2Vec2 tangent = b2RightPerp( normal );
1440
+ b2Vec2 p1 = point->point;
1441
+ b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->tangentImpulse, tangent );
1442
+ draw->DrawSegmentFcn( p1, p2, frictionColor, draw->context );
1443
+ snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.2f", point->tangentImpulse );
1444
+ draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context );
1445
+ }
1446
+ }
1447
+ }
1448
+ }
1449
+ }
1450
+
1451
+ if ( draw->drawIslands )
1452
+ {
1453
+ int count = world->islands.count;
1454
+ for ( int i = 0; i < count; ++i )
1455
+ {
1456
+ b2Island* island = world->islands.data + i;
1457
+ if ( island->setIndex == B2_NULL_INDEX )
1458
+ {
1459
+ continue;
1460
+ }
1461
+
1462
+ int shapeCount = 0;
1463
+ b2AABB aabb = {
1464
+ .lowerBound = { FLT_MAX, FLT_MAX },
1465
+ .upperBound = { -FLT_MAX, -FLT_MAX },
1466
+ };
1467
+
1468
+ int bodyId = island->headBody;
1469
+ while ( bodyId != B2_NULL_INDEX )
1470
+ {
1471
+ b2Body* body = b2BodyArray_Get( &world->bodies, bodyId );
1472
+ int shapeId = body->headShapeId;
1473
+ while ( shapeId != B2_NULL_INDEX )
1474
+ {
1475
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
1476
+ aabb = b2AABB_Union( aabb, shape->fatAABB );
1477
+ shapeCount += 1;
1478
+ shapeId = shape->nextShapeId;
1479
+ }
1480
+
1481
+ bodyId = body->islandNext;
1482
+ }
1483
+
1484
+ if ( shapeCount > 0 )
1485
+ {
1486
+ b2Vec2 vs[4] = { { aabb.lowerBound.x, aabb.lowerBound.y },
1487
+ { aabb.upperBound.x, aabb.lowerBound.y },
1488
+ { aabb.upperBound.x, aabb.upperBound.y },
1489
+ { aabb.lowerBound.x, aabb.upperBound.y } };
1490
+
1491
+ draw->DrawPolygonFcn( vs, 4, b2_colorOrangeRed, draw->context );
1492
+ }
1493
+ }
1494
+ }
1495
+ }
1496
+
1497
+ b2BodyEvents b2World_GetBodyEvents( b2WorldId worldId )
1498
+ {
1499
+ b2World* world = b2GetWorldFromId( worldId );
1500
+ B2_ASSERT( world->locked == false );
1501
+ if ( world->locked )
1502
+ {
1503
+ return (b2BodyEvents){ 0 };
1504
+ }
1505
+
1506
+ int count = world->bodyMoveEvents.count;
1507
+ b2BodyEvents events = { world->bodyMoveEvents.data, count };
1508
+ return events;
1509
+ }
1510
+
1511
+ b2SensorEvents b2World_GetSensorEvents( b2WorldId worldId )
1512
+ {
1513
+ b2World* world = b2GetWorldFromId( worldId );
1514
+ B2_ASSERT( world->locked == false );
1515
+ if ( world->locked )
1516
+ {
1517
+ return (b2SensorEvents){ 0 };
1518
+ }
1519
+
1520
+ // Careful to use previous buffer
1521
+ int endEventArrayIndex = 1 - world->endEventArrayIndex;
1522
+
1523
+ int beginCount = world->sensorBeginEvents.count;
1524
+ int endCount = world->sensorEndEvents[endEventArrayIndex].count;
1525
+
1526
+ b2SensorEvents events = {
1527
+ .beginEvents = world->sensorBeginEvents.data,
1528
+ .endEvents = world->sensorEndEvents[endEventArrayIndex].data,
1529
+ .beginCount = beginCount,
1530
+ .endCount = endCount,
1531
+ };
1532
+ return events;
1533
+ }
1534
+
1535
+ b2ContactEvents b2World_GetContactEvents( b2WorldId worldId )
1536
+ {
1537
+ b2World* world = b2GetWorldFromId( worldId );
1538
+ B2_ASSERT( world->locked == false );
1539
+ if ( world->locked )
1540
+ {
1541
+ return (b2ContactEvents){ 0 };
1542
+ }
1543
+
1544
+ // Careful to use previous buffer
1545
+ int endEventArrayIndex = 1 - world->endEventArrayIndex;
1546
+
1547
+ int beginCount = world->contactBeginEvents.count;
1548
+ int endCount = world->contactEndEvents[endEventArrayIndex].count;
1549
+ int hitCount = world->contactHitEvents.count;
1550
+
1551
+ b2ContactEvents events = {
1552
+ .beginEvents = world->contactBeginEvents.data,
1553
+ .endEvents = world->contactEndEvents[endEventArrayIndex].data,
1554
+ .hitEvents = world->contactHitEvents.data,
1555
+ .beginCount = beginCount,
1556
+ .endCount = endCount,
1557
+ .hitCount = hitCount,
1558
+ };
1559
+
1560
+ return events;
1561
+ }
1562
+
1563
+ bool b2World_IsValid( b2WorldId id )
1564
+ {
1565
+ if ( id.index1 < 1 || B2_MAX_WORLDS < id.index1 )
1566
+ {
1567
+ return false;
1568
+ }
1569
+
1570
+ b2World* world = b2_worlds + ( id.index1 - 1 );
1571
+
1572
+ if ( world->worldId != id.index1 - 1 )
1573
+ {
1574
+ // world is not allocated
1575
+ return false;
1576
+ }
1577
+
1578
+ return id.generation == world->generation;
1579
+ }
1580
+
1581
+ bool b2Body_IsValid( b2BodyId id )
1582
+ {
1583
+ if ( B2_MAX_WORLDS <= id.world0 )
1584
+ {
1585
+ // invalid world
1586
+ return false;
1587
+ }
1588
+
1589
+ b2World* world = b2_worlds + id.world0;
1590
+ if ( world->worldId != id.world0 )
1591
+ {
1592
+ // world is free
1593
+ return false;
1594
+ }
1595
+
1596
+ if ( id.index1 < 1 || world->bodies.count < id.index1 )
1597
+ {
1598
+ // invalid index
1599
+ return false;
1600
+ }
1601
+
1602
+ b2Body* body = world->bodies.data + ( id.index1 - 1 );
1603
+ if ( body->setIndex == B2_NULL_INDEX )
1604
+ {
1605
+ // this was freed
1606
+ return false;
1607
+ }
1608
+
1609
+ B2_ASSERT( body->localIndex != B2_NULL_INDEX );
1610
+
1611
+ if ( body->generation != id.generation )
1612
+ {
1613
+ // this id is orphaned
1614
+ return false;
1615
+ }
1616
+
1617
+ return true;
1618
+ }
1619
+
1620
+ bool b2Shape_IsValid( b2ShapeId id )
1621
+ {
1622
+ if ( B2_MAX_WORLDS <= id.world0 )
1623
+ {
1624
+ return false;
1625
+ }
1626
+
1627
+ b2World* world = b2_worlds + id.world0;
1628
+ if ( world->worldId != id.world0 )
1629
+ {
1630
+ // world is free
1631
+ return false;
1632
+ }
1633
+
1634
+ int shapeId = id.index1 - 1;
1635
+ if ( shapeId < 0 || world->shapes.count <= shapeId )
1636
+ {
1637
+ return false;
1638
+ }
1639
+
1640
+ b2Shape* shape = world->shapes.data + shapeId;
1641
+ if ( shape->id == B2_NULL_INDEX )
1642
+ {
1643
+ // shape is free
1644
+ return false;
1645
+ }
1646
+
1647
+ B2_ASSERT( shape->id == shapeId );
1648
+
1649
+ return id.generation == shape->generation;
1650
+ }
1651
+
1652
+ bool b2Chain_IsValid( b2ChainId id )
1653
+ {
1654
+ if ( B2_MAX_WORLDS <= id.world0 )
1655
+ {
1656
+ return false;
1657
+ }
1658
+
1659
+ b2World* world = b2_worlds + id.world0;
1660
+ if ( world->worldId != id.world0 )
1661
+ {
1662
+ // world is free
1663
+ return false;
1664
+ }
1665
+
1666
+ int chainId = id.index1 - 1;
1667
+ if ( chainId < 0 || world->chainShapes.count <= chainId )
1668
+ {
1669
+ return false;
1670
+ }
1671
+
1672
+ b2ChainShape* chain = world->chainShapes.data + chainId;
1673
+ if ( chain->id == B2_NULL_INDEX )
1674
+ {
1675
+ // chain is free
1676
+ return false;
1677
+ }
1678
+
1679
+ B2_ASSERT( chain->id == chainId );
1680
+
1681
+ return id.generation == chain->generation;
1682
+ }
1683
+
1684
+ bool b2Joint_IsValid( b2JointId id )
1685
+ {
1686
+ if ( B2_MAX_WORLDS <= id.world0 )
1687
+ {
1688
+ return false;
1689
+ }
1690
+
1691
+ b2World* world = b2_worlds + id.world0;
1692
+ if ( world->worldId != id.world0 )
1693
+ {
1694
+ // world is free
1695
+ return false;
1696
+ }
1697
+
1698
+ int jointId = id.index1 - 1;
1699
+ if ( jointId < 0 || world->joints.count <= jointId )
1700
+ {
1701
+ return false;
1702
+ }
1703
+
1704
+ b2Joint* joint = world->joints.data + jointId;
1705
+ if ( joint->jointId == B2_NULL_INDEX )
1706
+ {
1707
+ // joint is free
1708
+ return false;
1709
+ }
1710
+
1711
+ B2_ASSERT( joint->jointId == jointId );
1712
+
1713
+ return id.generation == joint->generation;
1714
+ }
1715
+
1716
+ void b2World_EnableSleeping( b2WorldId worldId, bool flag )
1717
+ {
1718
+ b2World* world = b2GetWorldFromId( worldId );
1719
+ B2_ASSERT( world->locked == false );
1720
+ if ( world->locked )
1721
+ {
1722
+ return;
1723
+ }
1724
+
1725
+ if ( flag == world->enableSleep )
1726
+ {
1727
+ return;
1728
+ }
1729
+
1730
+ world->enableSleep = flag;
1731
+
1732
+ if ( flag == false )
1733
+ {
1734
+ int setCount = world->solverSets.count;
1735
+ for ( int i = b2_firstSleepingSet; i < setCount; ++i )
1736
+ {
1737
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, i );
1738
+ if ( set->bodySims.count > 0 )
1739
+ {
1740
+ b2WakeSolverSet( world, i );
1741
+ }
1742
+ }
1743
+ }
1744
+ }
1745
+
1746
+ bool b2World_IsSleepingEnabled( b2WorldId worldId )
1747
+ {
1748
+ b2World* world = b2GetWorldFromId( worldId );
1749
+ return world->enableSleep;
1750
+ }
1751
+
1752
+ void b2World_EnableWarmStarting( b2WorldId worldId, bool flag )
1753
+ {
1754
+ b2World* world = b2GetWorldFromId( worldId );
1755
+ B2_ASSERT( world->locked == false );
1756
+ if ( world->locked )
1757
+ {
1758
+ return;
1759
+ }
1760
+
1761
+ world->enableWarmStarting = flag;
1762
+ }
1763
+
1764
+ bool b2World_IsWarmStartingEnabled( b2WorldId worldId )
1765
+ {
1766
+ b2World* world = b2GetWorldFromId( worldId );
1767
+ return world->enableWarmStarting;
1768
+ }
1769
+
1770
+ int b2World_GetAwakeBodyCount( b2WorldId worldId )
1771
+ {
1772
+ b2World* world = b2GetWorldFromId( worldId );
1773
+ b2SolverSet* awakeSet = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
1774
+ return awakeSet->bodySims.count;
1775
+ }
1776
+
1777
+ void b2World_EnableContinuous( b2WorldId worldId, bool flag )
1778
+ {
1779
+ b2World* world = b2GetWorldFromId( worldId );
1780
+ B2_ASSERT( world->locked == false );
1781
+ if ( world->locked )
1782
+ {
1783
+ return;
1784
+ }
1785
+
1786
+ world->enableContinuous = flag;
1787
+ }
1788
+
1789
+ bool b2World_IsContinuousEnabled( b2WorldId worldId )
1790
+ {
1791
+ b2World* world = b2GetWorldFromId( worldId );
1792
+ return world->enableContinuous;
1793
+ }
1794
+
1795
+ void b2World_SetRestitutionThreshold( b2WorldId worldId, float value )
1796
+ {
1797
+ b2World* world = b2GetWorldFromId( worldId );
1798
+ B2_ASSERT( world->locked == false );
1799
+ if ( world->locked )
1800
+ {
1801
+ return;
1802
+ }
1803
+
1804
+ world->restitutionThreshold = b2ClampFloat( value, 0.0f, FLT_MAX );
1805
+ }
1806
+
1807
+ float b2World_GetRestitutionThreshold( b2WorldId worldId )
1808
+ {
1809
+ b2World* world = b2GetWorldFromId( worldId );
1810
+ return world->restitutionThreshold;
1811
+ }
1812
+
1813
+ void b2World_SetHitEventThreshold( b2WorldId worldId, float value )
1814
+ {
1815
+ b2World* world = b2GetWorldFromId( worldId );
1816
+ B2_ASSERT( world->locked == false );
1817
+ if ( world->locked )
1818
+ {
1819
+ return;
1820
+ }
1821
+
1822
+ world->hitEventThreshold = b2ClampFloat( value, 0.0f, FLT_MAX );
1823
+ }
1824
+
1825
+ float b2World_GetHitEventThreshold( b2WorldId worldId )
1826
+ {
1827
+ b2World* world = b2GetWorldFromId( worldId );
1828
+ return world->hitEventThreshold;
1829
+ }
1830
+
1831
+ void b2World_SetContactTuning( b2WorldId worldId, float hertz, float dampingRatio, float pushSpeed )
1832
+ {
1833
+ b2World* world = b2GetWorldFromId( worldId );
1834
+ B2_ASSERT( world->locked == false );
1835
+ if ( world->locked )
1836
+ {
1837
+ return;
1838
+ }
1839
+
1840
+ world->contactHertz = b2ClampFloat( hertz, 0.0f, FLT_MAX );
1841
+ world->contactDampingRatio = b2ClampFloat( dampingRatio, 0.0f, FLT_MAX );
1842
+ world->maxContactPushSpeed = b2ClampFloat( pushSpeed, 0.0f, FLT_MAX );
1843
+ }
1844
+
1845
+ void b2World_SetJointTuning( b2WorldId worldId, float hertz, float dampingRatio )
1846
+ {
1847
+ b2World* world = b2GetWorldFromId( worldId );
1848
+ B2_ASSERT( world->locked == false );
1849
+ if ( world->locked )
1850
+ {
1851
+ return;
1852
+ }
1853
+
1854
+ world->jointHertz = b2ClampFloat( hertz, 0.0f, FLT_MAX );
1855
+ world->jointDampingRatio = b2ClampFloat( dampingRatio, 0.0f, FLT_MAX );
1856
+ }
1857
+
1858
+ void b2World_SetMaximumLinearSpeed( b2WorldId worldId, float maximumLinearSpeed )
1859
+ {
1860
+ B2_ASSERT( b2IsValidFloat( maximumLinearSpeed ) && maximumLinearSpeed > 0.0f );
1861
+
1862
+ b2World* world = b2GetWorldFromId( worldId );
1863
+ B2_ASSERT( world->locked == false );
1864
+ if ( world->locked )
1865
+ {
1866
+ return;
1867
+ }
1868
+
1869
+ world->maxLinearSpeed = maximumLinearSpeed;
1870
+ }
1871
+
1872
+ float b2World_GetMaximumLinearSpeed( b2WorldId worldId )
1873
+ {
1874
+ b2World* world = b2GetWorldFromId( worldId );
1875
+ return world->maxLinearSpeed;
1876
+ }
1877
+
1878
+ b2Profile b2World_GetProfile( b2WorldId worldId )
1879
+ {
1880
+ b2World* world = b2GetWorldFromId( worldId );
1881
+ return world->profile;
1882
+ }
1883
+
1884
+ b2Counters b2World_GetCounters( b2WorldId worldId )
1885
+ {
1886
+ b2World* world = b2GetWorldFromId( worldId );
1887
+ b2Counters s = { 0 };
1888
+ s.bodyCount = b2GetIdCount( &world->bodyIdPool );
1889
+ s.shapeCount = b2GetIdCount( &world->shapeIdPool );
1890
+ s.contactCount = b2GetIdCount( &world->contactIdPool );
1891
+ s.jointCount = b2GetIdCount( &world->jointIdPool );
1892
+ s.islandCount = b2GetIdCount( &world->islandIdPool );
1893
+
1894
+ b2DynamicTree* staticTree = world->broadPhase.trees + b2_staticBody;
1895
+ s.staticTreeHeight = b2DynamicTree_GetHeight( staticTree );
1896
+
1897
+ b2DynamicTree* dynamicTree = world->broadPhase.trees + b2_dynamicBody;
1898
+ b2DynamicTree* kinematicTree = world->broadPhase.trees + b2_kinematicBody;
1899
+ s.treeHeight = b2MaxInt( b2DynamicTree_GetHeight( dynamicTree ), b2DynamicTree_GetHeight( kinematicTree ) );
1900
+
1901
+ s.stackUsed = b2GetMaxArenaAllocation( &world->arena );
1902
+ s.byteCount = b2GetByteCount();
1903
+ s.taskCount = world->taskCount;
1904
+
1905
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT; ++i )
1906
+ {
1907
+ s.colorCounts[i] = world->constraintGraph.colors[i].contactSims.count + world->constraintGraph.colors[i].jointSims.count;
1908
+ }
1909
+ return s;
1910
+ }
1911
+
1912
+ void b2World_SetUserData( b2WorldId worldId, void* userData )
1913
+ {
1914
+ b2World* world = b2GetWorldFromId( worldId );
1915
+ world->userData = userData;
1916
+ }
1917
+
1918
+ void* b2World_GetUserData( b2WorldId worldId )
1919
+ {
1920
+ b2World* world = b2GetWorldFromId( worldId );
1921
+ return world->userData;
1922
+ }
1923
+
1924
+ void b2World_SetFrictionCallback( b2WorldId worldId, b2FrictionCallback* callback )
1925
+ {
1926
+ b2World* world = b2GetWorldFromId( worldId );
1927
+ if ( world->locked )
1928
+ {
1929
+ return;
1930
+ }
1931
+
1932
+ if ( callback != NULL )
1933
+ {
1934
+ world->frictionCallback = callback;
1935
+ }
1936
+ else
1937
+ {
1938
+ world->frictionCallback = b2DefaultFrictionCallback;
1939
+ }
1940
+ }
1941
+
1942
+ void b2World_SetRestitutionCallback( b2WorldId worldId, b2RestitutionCallback* callback )
1943
+ {
1944
+ b2World* world = b2GetWorldFromId( worldId );
1945
+ if ( world->locked )
1946
+ {
1947
+ return;
1948
+ }
1949
+
1950
+ if ( callback != NULL )
1951
+ {
1952
+ world->restitutionCallback = callback;
1953
+ }
1954
+ else
1955
+ {
1956
+ world->restitutionCallback = b2DefaultRestitutionCallback;
1957
+ }
1958
+ }
1959
+
1960
+ void b2World_DumpMemoryStats( b2WorldId worldId )
1961
+ {
1962
+ FILE* file = fopen( "box2d_memory.txt", "w" );
1963
+ if ( file == NULL )
1964
+ {
1965
+ return;
1966
+ }
1967
+
1968
+ b2World* world = b2GetWorldFromId( worldId );
1969
+
1970
+ // id pools
1971
+ fprintf( file, "id pools\n" );
1972
+ fprintf( file, "body ids: %d\n", b2GetIdBytes( &world->bodyIdPool ) );
1973
+ fprintf( file, "solver set ids: %d\n", b2GetIdBytes( &world->solverSetIdPool ) );
1974
+ fprintf( file, "joint ids: %d\n", b2GetIdBytes( &world->jointIdPool ) );
1975
+ fprintf( file, "contact ids: %d\n", b2GetIdBytes( &world->contactIdPool ) );
1976
+ fprintf( file, "island ids: %d\n", b2GetIdBytes( &world->islandIdPool ) );
1977
+ fprintf( file, "shape ids: %d\n", b2GetIdBytes( &world->shapeIdPool ) );
1978
+ fprintf( file, "chain ids: %d\n", b2GetIdBytes( &world->chainIdPool ) );
1979
+ fprintf( file, "\n" );
1980
+
1981
+ // world arrays
1982
+ fprintf( file, "world arrays\n" );
1983
+ fprintf( file, "bodies: %d\n", b2BodyArray_ByteCount( &world->bodies ) );
1984
+ fprintf( file, "solver sets: %d\n", b2SolverSetArray_ByteCount( &world->solverSets ) );
1985
+ fprintf( file, "joints: %d\n", b2JointArray_ByteCount( &world->joints ) );
1986
+ fprintf( file, "contacts: %d\n", b2ContactArray_ByteCount( &world->contacts ) );
1987
+ fprintf( file, "islands: %d\n", b2IslandArray_ByteCount( &world->islands ) );
1988
+ fprintf( file, "shapes: %d\n", b2ShapeArray_ByteCount( &world->shapes ) );
1989
+ fprintf( file, "chains: %d\n", b2ChainShapeArray_ByteCount( &world->chainShapes ) );
1990
+ fprintf( file, "\n" );
1991
+
1992
+ // broad-phase
1993
+ fprintf( file, "broad-phase\n" );
1994
+ fprintf( file, "static tree: %d\n", b2DynamicTree_GetByteCount( world->broadPhase.trees + b2_staticBody ) );
1995
+ fprintf( file, "kinematic tree: %d\n", b2DynamicTree_GetByteCount( world->broadPhase.trees + b2_kinematicBody ) );
1996
+ fprintf( file, "dynamic tree: %d\n", b2DynamicTree_GetByteCount( world->broadPhase.trees + b2_dynamicBody ) );
1997
+ b2HashSet* moveSet = &world->broadPhase.moveSet;
1998
+ fprintf( file, "moveSet: %d (%d, %d)\n", b2GetHashSetBytes( moveSet ), moveSet->count, moveSet->capacity );
1999
+ fprintf( file, "moveArray: %d\n", b2IntArray_ByteCount( &world->broadPhase.moveArray ) );
2000
+ b2HashSet* pairSet = &world->broadPhase.pairSet;
2001
+ fprintf( file, "pairSet: %d (%d, %d)\n", b2GetHashSetBytes( pairSet ), pairSet->count, pairSet->capacity );
2002
+ fprintf( file, "\n" );
2003
+
2004
+ // solver sets
2005
+ int bodySimCapacity = 0;
2006
+ int bodyStateCapacity = 0;
2007
+ int jointSimCapacity = 0;
2008
+ int contactSimCapacity = 0;
2009
+ int islandSimCapacity = 0;
2010
+ int solverSetCapacity = world->solverSets.count;
2011
+ for ( int i = 0; i < solverSetCapacity; ++i )
2012
+ {
2013
+ b2SolverSet* set = world->solverSets.data + i;
2014
+ if ( set->setIndex == B2_NULL_INDEX )
2015
+ {
2016
+ continue;
2017
+ }
2018
+
2019
+ bodySimCapacity += set->bodySims.capacity;
2020
+ bodyStateCapacity += set->bodyStates.capacity;
2021
+ jointSimCapacity += set->jointSims.capacity;
2022
+ contactSimCapacity += set->contactSims.capacity;
2023
+ islandSimCapacity += set->islandSims.capacity;
2024
+ }
2025
+
2026
+ fprintf( file, "solver sets\n" );
2027
+ fprintf( file, "body sim: %d\n", bodySimCapacity * (int)sizeof( b2BodySim ) );
2028
+ fprintf( file, "body state: %d\n", bodyStateCapacity * (int)sizeof( b2BodyState ) );
2029
+ fprintf( file, "joint sim: %d\n", jointSimCapacity * (int)sizeof( b2JointSim ) );
2030
+ fprintf( file, "contact sim: %d\n", contactSimCapacity * (int)sizeof( b2ContactSim ) );
2031
+ fprintf( file, "island sim: %d\n", islandSimCapacity * (int)sizeof( islandSimCapacity ) );
2032
+ fprintf( file, "\n" );
2033
+
2034
+ // constraint graph
2035
+ int bodyBitSetBytes = 0;
2036
+ contactSimCapacity = 0;
2037
+ jointSimCapacity = 0;
2038
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT; ++i )
2039
+ {
2040
+ b2GraphColor* c = world->constraintGraph.colors + i;
2041
+ bodyBitSetBytes += b2GetBitSetBytes( &c->bodySet );
2042
+ contactSimCapacity += c->contactSims.capacity;
2043
+ jointSimCapacity += c->jointSims.capacity;
2044
+ }
2045
+
2046
+ fprintf( file, "constraint graph\n" );
2047
+ fprintf( file, "body bit sets: %d\n", bodyBitSetBytes );
2048
+ fprintf( file, "joint sim: %d\n", jointSimCapacity * (int)sizeof( b2JointSim ) );
2049
+ fprintf( file, "contact sim: %d\n", contactSimCapacity * (int)sizeof( b2ContactSim ) );
2050
+ fprintf( file, "\n" );
2051
+
2052
+ // stack allocator
2053
+ fprintf( file, "stack allocator: %d\n\n", world->arena.capacity );
2054
+
2055
+ // chain shapes
2056
+ // todo
2057
+
2058
+ fclose( file );
2059
+ }
2060
+
2061
+ typedef struct WorldQueryContext
2062
+ {
2063
+ b2World* world;
2064
+ b2OverlapResultFcn* fcn;
2065
+ b2QueryFilter filter;
2066
+ void* userContext;
2067
+ } WorldQueryContext;
2068
+
2069
+ static bool TreeQueryCallback( int proxyId, uint64_t userData, void* context )
2070
+ {
2071
+ B2_UNUSED( proxyId );
2072
+
2073
+ int shapeId = (int)userData;
2074
+
2075
+ WorldQueryContext* worldContext = context;
2076
+ b2World* world = worldContext->world;
2077
+
2078
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2079
+
2080
+ b2Filter shapeFilter = shape->filter;
2081
+ b2QueryFilter queryFilter = worldContext->filter;
2082
+
2083
+ if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 )
2084
+ {
2085
+ return true;
2086
+ }
2087
+
2088
+ b2ShapeId id = { shapeId + 1, world->worldId, shape->generation };
2089
+ bool result = worldContext->fcn( id, worldContext->userContext );
2090
+ return result;
2091
+ }
2092
+
2093
+ b2TreeStats b2World_OverlapAABB( b2WorldId worldId, b2AABB aabb, b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context )
2094
+ {
2095
+ b2TreeStats treeStats = { 0 };
2096
+
2097
+ b2World* world = b2GetWorldFromId( worldId );
2098
+ B2_ASSERT( world->locked == false );
2099
+ if ( world->locked )
2100
+ {
2101
+ return treeStats;
2102
+ }
2103
+
2104
+ B2_ASSERT( b2IsValidAABB( aabb ) );
2105
+
2106
+ WorldQueryContext worldContext = { world, fcn, filter, context };
2107
+
2108
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2109
+ {
2110
+ b2TreeStats treeResult =
2111
+ b2DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, TreeQueryCallback, &worldContext );
2112
+
2113
+ treeStats.nodeVisits += treeResult.nodeVisits;
2114
+ treeStats.leafVisits += treeResult.leafVisits;
2115
+ }
2116
+
2117
+ return treeStats;
2118
+ }
2119
+
2120
+ typedef struct WorldOverlapContext
2121
+ {
2122
+ b2World* world;
2123
+ b2OverlapResultFcn* fcn;
2124
+ b2QueryFilter filter;
2125
+ const b2ShapeProxy* proxy;
2126
+ void* userContext;
2127
+ } WorldOverlapContext;
2128
+
2129
+ static bool TreeOverlapCallback( int proxyId, uint64_t userData, void* context )
2130
+ {
2131
+ B2_UNUSED( proxyId );
2132
+
2133
+ int shapeId = (int)userData;
2134
+
2135
+ WorldOverlapContext* worldContext = context;
2136
+ b2World* world = worldContext->world;
2137
+
2138
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2139
+
2140
+ b2Filter shapeFilter = shape->filter;
2141
+ b2QueryFilter queryFilter = worldContext->filter;
2142
+
2143
+ if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 )
2144
+ {
2145
+ return true;
2146
+ }
2147
+
2148
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
2149
+ b2Transform transform = b2GetBodyTransformQuick( world, body );
2150
+
2151
+ b2DistanceInput input;
2152
+ input.proxyA = *worldContext->proxy;
2153
+ input.proxyB = b2MakeShapeDistanceProxy( shape );
2154
+ input.transformA = b2Transform_identity;
2155
+ input.transformB = transform;
2156
+ input.useRadii = true;
2157
+
2158
+ b2SimplexCache cache = { 0 };
2159
+ b2DistanceOutput output = b2ShapeDistance( &input, &cache, NULL, 0 );
2160
+
2161
+ float tolerance = 0.1f * B2_LINEAR_SLOP;
2162
+ if ( output.distance > tolerance )
2163
+ {
2164
+ return true;
2165
+ }
2166
+
2167
+ b2ShapeId id = { shape->id + 1, world->worldId, shape->generation };
2168
+ bool result = worldContext->fcn( id, worldContext->userContext );
2169
+ return result;
2170
+ }
2171
+
2172
+ b2TreeStats b2World_OverlapShape( b2WorldId worldId, const b2ShapeProxy* proxy, b2QueryFilter filter, b2OverlapResultFcn* fcn,
2173
+ void* context )
2174
+ {
2175
+ b2TreeStats treeStats = { 0 };
2176
+
2177
+ b2World* world = b2GetWorldFromId( worldId );
2178
+ B2_ASSERT( world->locked == false );
2179
+ if ( world->locked )
2180
+ {
2181
+ return treeStats;
2182
+ }
2183
+
2184
+ b2AABB aabb = b2MakeAABB( proxy->points, proxy->count, proxy->radius );
2185
+ WorldOverlapContext worldContext = {
2186
+ world, fcn, filter, proxy, context,
2187
+ };
2188
+
2189
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2190
+ {
2191
+ b2TreeStats treeResult =
2192
+ b2DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, TreeOverlapCallback, &worldContext );
2193
+
2194
+ treeStats.nodeVisits += treeResult.nodeVisits;
2195
+ treeStats.leafVisits += treeResult.leafVisits;
2196
+ }
2197
+
2198
+ return treeStats;
2199
+ }
2200
+
2201
+ typedef struct WorldRayCastContext
2202
+ {
2203
+ b2World* world;
2204
+ b2CastResultFcn* fcn;
2205
+ b2QueryFilter filter;
2206
+ float fraction;
2207
+ void* userContext;
2208
+ } WorldRayCastContext;
2209
+
2210
+ static float RayCastCallback( const b2RayCastInput* input, int proxyId, uint64_t userData, void* context )
2211
+ {
2212
+ B2_UNUSED( proxyId );
2213
+
2214
+ int shapeId = (int)userData;
2215
+
2216
+ WorldRayCastContext* worldContext = context;
2217
+ b2World* world = worldContext->world;
2218
+
2219
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2220
+ b2Filter shapeFilter = shape->filter;
2221
+ b2QueryFilter queryFilter = worldContext->filter;
2222
+
2223
+ if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 )
2224
+ {
2225
+ return input->maxFraction;
2226
+ }
2227
+
2228
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
2229
+ b2Transform transform = b2GetBodyTransformQuick( world, body );
2230
+ b2CastOutput output = b2RayCastShape( input, shape, transform );
2231
+
2232
+ if ( output.hit )
2233
+ {
2234
+ b2ShapeId id = { shapeId + 1, world->worldId, shape->generation };
2235
+ float fraction = worldContext->fcn( id, output.point, output.normal, output.fraction, worldContext->userContext );
2236
+
2237
+ // The user may return -1 to skip this shape
2238
+ if ( 0.0f <= fraction && fraction <= 1.0f )
2239
+ {
2240
+ worldContext->fraction = fraction;
2241
+ }
2242
+
2243
+ return fraction;
2244
+ }
2245
+
2246
+ return input->maxFraction;
2247
+ }
2248
+
2249
+ b2TreeStats b2World_CastRay( b2WorldId worldId, b2Vec2 origin, b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn,
2250
+ void* context )
2251
+ {
2252
+ b2TreeStats treeStats = { 0 };
2253
+
2254
+ b2World* world = b2GetWorldFromId( worldId );
2255
+ B2_ASSERT( world->locked == false );
2256
+ if ( world->locked )
2257
+ {
2258
+ return treeStats;
2259
+ }
2260
+
2261
+ B2_ASSERT( b2IsValidVec2( origin ) );
2262
+ B2_ASSERT( b2IsValidVec2( translation ) );
2263
+
2264
+ b2RayCastInput input = { origin, translation, 1.0f };
2265
+
2266
+ WorldRayCastContext worldContext = { world, fcn, filter, 1.0f, context };
2267
+
2268
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2269
+ {
2270
+ b2TreeStats treeResult =
2271
+ b2DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, RayCastCallback, &worldContext );
2272
+ treeStats.nodeVisits += treeResult.nodeVisits;
2273
+ treeStats.leafVisits += treeResult.leafVisits;
2274
+
2275
+ if ( worldContext.fraction == 0.0f )
2276
+ {
2277
+ return treeStats;
2278
+ }
2279
+
2280
+ input.maxFraction = worldContext.fraction;
2281
+ }
2282
+
2283
+ return treeStats;
2284
+ }
2285
+
2286
+ // This callback finds the closest hit. This is the most common callback used in games.
2287
+ static float b2RayCastClosestFcn( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context )
2288
+ {
2289
+ b2RayResult* rayResult = (b2RayResult*)context;
2290
+ rayResult->shapeId = shapeId;
2291
+ rayResult->point = point;
2292
+ rayResult->normal = normal;
2293
+ rayResult->fraction = fraction;
2294
+ rayResult->hit = true;
2295
+ return fraction;
2296
+ }
2297
+
2298
+ b2RayResult b2World_CastRayClosest( b2WorldId worldId, b2Vec2 origin, b2Vec2 translation, b2QueryFilter filter )
2299
+ {
2300
+ b2RayResult result = { 0 };
2301
+
2302
+ b2World* world = b2GetWorldFromId( worldId );
2303
+ B2_ASSERT( world->locked == false );
2304
+ if ( world->locked )
2305
+ {
2306
+ return result;
2307
+ }
2308
+
2309
+ B2_ASSERT( b2IsValidVec2( origin ) );
2310
+ B2_ASSERT( b2IsValidVec2( translation ) );
2311
+
2312
+ b2RayCastInput input = { origin, translation, 1.0f };
2313
+ WorldRayCastContext worldContext = { world, b2RayCastClosestFcn, filter, 1.0f, &result };
2314
+
2315
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2316
+ {
2317
+ b2TreeStats treeResult =
2318
+ b2DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, RayCastCallback, &worldContext );
2319
+ result.nodeVisits += treeResult.nodeVisits;
2320
+ result.leafVisits += treeResult.leafVisits;
2321
+
2322
+ if ( worldContext.fraction == 0.0f )
2323
+ {
2324
+ return result;
2325
+ }
2326
+
2327
+ input.maxFraction = worldContext.fraction;
2328
+ }
2329
+
2330
+ return result;
2331
+ }
2332
+
2333
+ static float ShapeCastCallback( const b2ShapeCastInput* input, int proxyId, uint64_t userData, void* context )
2334
+ {
2335
+ B2_UNUSED( proxyId );
2336
+
2337
+ int shapeId = (int)userData;
2338
+
2339
+ WorldRayCastContext* worldContext = context;
2340
+ b2World* world = worldContext->world;
2341
+
2342
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2343
+ b2Filter shapeFilter = shape->filter;
2344
+ b2QueryFilter queryFilter = worldContext->filter;
2345
+
2346
+ if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 )
2347
+ {
2348
+ return input->maxFraction;
2349
+ }
2350
+
2351
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
2352
+ b2Transform transform = b2GetBodyTransformQuick( world, body );
2353
+
2354
+ b2CastOutput output = b2ShapeCastShape( input, shape, transform );
2355
+
2356
+ if ( output.hit )
2357
+ {
2358
+ b2ShapeId id = { shapeId + 1, world->worldId, shape->generation };
2359
+ float fraction = worldContext->fcn( id, output.point, output.normal, output.fraction, worldContext->userContext );
2360
+
2361
+ // The user may return -1 to skip this shape
2362
+ if ( 0.0f <= fraction && fraction <= 1.0f )
2363
+ {
2364
+ worldContext->fraction = fraction;
2365
+ }
2366
+
2367
+ return fraction;
2368
+ }
2369
+
2370
+ return input->maxFraction;
2371
+ }
2372
+
2373
+ b2TreeStats b2World_CastShape( b2WorldId worldId, const b2ShapeProxy* proxy, b2Vec2 translation, b2QueryFilter filter,
2374
+ b2CastResultFcn* fcn, void* context )
2375
+ {
2376
+ b2TreeStats treeStats = { 0 };
2377
+
2378
+ b2World* world = b2GetWorldFromId( worldId );
2379
+ B2_ASSERT( world->locked == false );
2380
+ if ( world->locked )
2381
+ {
2382
+ return treeStats;
2383
+ }
2384
+
2385
+ B2_ASSERT( b2IsValidVec2( translation ) );
2386
+
2387
+ b2ShapeCastInput input = { 0 };
2388
+ input.proxy = *proxy;
2389
+ input.translation = translation;
2390
+ input.maxFraction = 1.0f;
2391
+
2392
+ WorldRayCastContext worldContext = { world, fcn, filter, 1.0f, context };
2393
+
2394
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2395
+ {
2396
+ b2TreeStats treeResult =
2397
+ b2DynamicTree_ShapeCast( world->broadPhase.trees + i, &input, filter.maskBits, ShapeCastCallback, &worldContext );
2398
+ treeStats.nodeVisits += treeResult.nodeVisits;
2399
+ treeStats.leafVisits += treeResult.leafVisits;
2400
+
2401
+ if ( worldContext.fraction == 0.0f )
2402
+ {
2403
+ return treeStats;
2404
+ }
2405
+
2406
+ input.maxFraction = worldContext.fraction;
2407
+ }
2408
+
2409
+ return treeStats;
2410
+ }
2411
+
2412
+ typedef struct b2MoverContext
2413
+ {
2414
+ b2World* world;
2415
+ b2QueryFilter filter;
2416
+ b2ShapeProxy proxy;
2417
+ b2Transform transform;
2418
+ void* userContext;
2419
+ } b2CharacterCallbackContext;
2420
+
2421
+ typedef struct WorldMoverCastContext
2422
+ {
2423
+ b2World* world;
2424
+ b2QueryFilter filter;
2425
+ float fraction;
2426
+ } WorldMoverCastContext;
2427
+
2428
+ static float MoverCastCallback( const b2ShapeCastInput* input, int proxyId, uint64_t userData, void* context )
2429
+ {
2430
+ B2_UNUSED( proxyId );
2431
+
2432
+ int shapeId = (int)userData;
2433
+ WorldMoverCastContext* worldContext = context;
2434
+ b2World* world = worldContext->world;
2435
+
2436
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2437
+ b2Filter shapeFilter = shape->filter;
2438
+ b2QueryFilter queryFilter = worldContext->filter;
2439
+
2440
+ if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 )
2441
+ {
2442
+ return worldContext->fraction;
2443
+ }
2444
+
2445
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
2446
+ b2Transform transform = b2GetBodyTransformQuick( world, body );
2447
+
2448
+ b2CastOutput output = b2ShapeCastShape( input, shape, transform );
2449
+ if ( output.fraction == 0.0f )
2450
+ {
2451
+ // Ignore overlapping shapes
2452
+ return worldContext->fraction;
2453
+ }
2454
+
2455
+ worldContext->fraction = output.fraction;
2456
+ return output.fraction;
2457
+ }
2458
+
2459
+ float b2World_CastMover( b2WorldId worldId, const b2Capsule* mover, b2Vec2 translation, b2QueryFilter filter )
2460
+ {
2461
+ B2_ASSERT( b2IsValidVec2( translation ) );
2462
+ B2_ASSERT( mover->radius > 2.0f * B2_LINEAR_SLOP );
2463
+
2464
+ b2World* world = b2GetWorldFromId( worldId );
2465
+ B2_ASSERT( world->locked == false );
2466
+ if ( world->locked )
2467
+ {
2468
+ return 1.0f;
2469
+ }
2470
+
2471
+ b2ShapeCastInput input = { 0 };
2472
+ input.proxy.points[0] = mover->center1;
2473
+ input.proxy.points[1] = mover->center2;
2474
+ input.proxy.count = 2;
2475
+ input.proxy.radius = mover->radius;
2476
+ input.translation = translation;
2477
+ input.maxFraction = 1.0f;
2478
+ input.canEncroach = true;
2479
+
2480
+ WorldMoverCastContext worldContext = { world, filter, 1.0f };
2481
+
2482
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2483
+ {
2484
+ b2DynamicTree_ShapeCast( world->broadPhase.trees + i, &input, filter.maskBits, MoverCastCallback, &worldContext );
2485
+
2486
+ if ( worldContext.fraction == 0.0f )
2487
+ {
2488
+ return 0.0f;
2489
+ }
2490
+
2491
+ input.maxFraction = worldContext.fraction;
2492
+ }
2493
+
2494
+ return worldContext.fraction;
2495
+ }
2496
+
2497
+ typedef struct WorldMoverContext
2498
+ {
2499
+ b2World* world;
2500
+ b2PlaneResultFcn* fcn;
2501
+ b2QueryFilter filter;
2502
+ b2Capsule mover;
2503
+ void* userContext;
2504
+ } WorldMoverContext;
2505
+
2506
+ static bool TreeCollideCallback( int proxyId, uint64_t userData, void* context )
2507
+ {
2508
+ B2_UNUSED( proxyId );
2509
+
2510
+ int shapeId = (int)userData;
2511
+ WorldMoverContext* worldContext = (WorldMoverContext*)context;
2512
+ b2World* world = worldContext->world;
2513
+
2514
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2515
+
2516
+ b2Filter shapeFilter = shape->filter;
2517
+ b2QueryFilter queryFilter = worldContext->filter;
2518
+
2519
+ if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 )
2520
+ {
2521
+ return true;
2522
+ }
2523
+
2524
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
2525
+ b2Transform transform = b2GetBodyTransformQuick( world, body );
2526
+
2527
+ b2PlaneResult result = b2CollideMover( shape, transform, &worldContext->mover );
2528
+
2529
+ if ( result.hit )
2530
+ {
2531
+ b2ShapeId id = { shape->id + 1, world->worldId, shape->generation };
2532
+ return worldContext->fcn( id, &result, worldContext->userContext );
2533
+ }
2534
+
2535
+ return true;
2536
+ }
2537
+
2538
+ // It is tempting to use a shape proxy for the mover, but this makes handling deep overlap difficult and the generality may
2539
+ // not be worth it.
2540
+ void b2World_CollideMover( b2WorldId worldId, const b2Capsule* mover, b2QueryFilter filter, b2PlaneResultFcn* fcn, void* context )
2541
+ {
2542
+ b2World* world = b2GetWorldFromId( worldId );
2543
+ B2_ASSERT( world->locked == false );
2544
+ if ( world->locked )
2545
+ {
2546
+ return;
2547
+ }
2548
+
2549
+ b2Vec2 r = { mover->radius, mover->radius };
2550
+
2551
+ b2AABB aabb;
2552
+ aabb.lowerBound = b2Sub( b2Min( mover->center1, mover->center2 ), r );
2553
+ aabb.upperBound = b2Add( b2Max( mover->center1, mover->center2 ), r );
2554
+
2555
+ WorldMoverContext worldContext = {
2556
+ world, fcn, filter, *mover, context,
2557
+ };
2558
+
2559
+ for ( int i = 0; i < b2_bodyTypeCount; ++i )
2560
+ {
2561
+ b2DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, TreeCollideCallback, &worldContext );
2562
+ }
2563
+ }
2564
+
2565
+ #if 0
2566
+
2567
+ void b2World_Dump()
2568
+ {
2569
+ if (m_locked)
2570
+ {
2571
+ return;
2572
+ }
2573
+
2574
+ b2OpenDump("box2d_dump.inl");
2575
+
2576
+ b2Dump("b2Vec2 g(%.9g, %.9g);\n", m_gravity.x, m_gravity.y);
2577
+ b2Dump("m_world->SetGravity(g);\n");
2578
+
2579
+ b2Dump("b2Body** sims = (b2Body**)b2Alloc(%d * sizeof(b2Body*));\n", m_bodyCount);
2580
+ b2Dump("b2Joint** joints = (b2Joint**)b2Alloc(%d * sizeof(b2Joint*));\n", m_jointCount);
2581
+
2582
+ int32 i = 0;
2583
+ for (b2Body* b = m_bodyList; b; b = b->m_next)
2584
+ {
2585
+ b->m_islandIndex = i;
2586
+ b->Dump();
2587
+ ++i;
2588
+ }
2589
+
2590
+ i = 0;
2591
+ for (b2Joint* j = m_jointList; j; j = j->m_next)
2592
+ {
2593
+ j->m_index = i;
2594
+ ++i;
2595
+ }
2596
+
2597
+ // First pass on joints, skip gear joints.
2598
+ for (b2Joint* j = m_jointList; j; j = j->m_next)
2599
+ {
2600
+ if (j->m_type == e_gearJoint)
2601
+ {
2602
+ continue;
2603
+ }
2604
+
2605
+ b2Dump("{\n");
2606
+ j->Dump();
2607
+ b2Dump("}\n");
2608
+ }
2609
+
2610
+ // Second pass on joints, only gear joints.
2611
+ for (b2Joint* j = m_jointList; j; j = j->m_next)
2612
+ {
2613
+ if (j->m_type != e_gearJoint)
2614
+ {
2615
+ continue;
2616
+ }
2617
+
2618
+ b2Dump("{\n");
2619
+ j->Dump();
2620
+ b2Dump("}\n");
2621
+ }
2622
+
2623
+ b2Dump("b2Free(joints);\n");
2624
+ b2Dump("b2Free(sims);\n");
2625
+ b2Dump("joints = nullptr;\n");
2626
+ b2Dump("sims = nullptr;\n");
2627
+
2628
+ b2CloseDump();
2629
+ }
2630
+ #endif
2631
+
2632
+ void b2World_SetCustomFilterCallback( b2WorldId worldId, b2CustomFilterFcn* fcn, void* context )
2633
+ {
2634
+ b2World* world = b2GetWorldFromId( worldId );
2635
+ world->customFilterFcn = fcn;
2636
+ world->customFilterContext = context;
2637
+ }
2638
+
2639
+ void b2World_SetPreSolveCallback( b2WorldId worldId, b2PreSolveFcn* fcn, void* context )
2640
+ {
2641
+ b2World* world = b2GetWorldFromId( worldId );
2642
+ world->preSolveFcn = fcn;
2643
+ world->preSolveContext = context;
2644
+ }
2645
+
2646
+ void b2World_SetGravity( b2WorldId worldId, b2Vec2 gravity )
2647
+ {
2648
+ b2World* world = b2GetWorldFromId( worldId );
2649
+ world->gravity = gravity;
2650
+ }
2651
+
2652
+ b2Vec2 b2World_GetGravity( b2WorldId worldId )
2653
+ {
2654
+ b2World* world = b2GetWorldFromId( worldId );
2655
+ return world->gravity;
2656
+ }
2657
+
2658
+ struct ExplosionContext
2659
+ {
2660
+ b2World* world;
2661
+ b2Vec2 position;
2662
+ float radius;
2663
+ float falloff;
2664
+ float impulsePerLength;
2665
+ };
2666
+
2667
+ static bool ExplosionCallback( int proxyId, uint64_t userData, void* context )
2668
+ {
2669
+ B2_UNUSED( proxyId );
2670
+
2671
+ int shapeId = (int)userData;
2672
+
2673
+ struct ExplosionContext* explosionContext = context;
2674
+ b2World* world = explosionContext->world;
2675
+
2676
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2677
+
2678
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
2679
+ B2_ASSERT( body->type == b2_dynamicBody );
2680
+
2681
+ b2Transform transform = b2GetBodyTransformQuick( world, body );
2682
+
2683
+ b2DistanceInput input;
2684
+ input.proxyA = b2MakeShapeDistanceProxy( shape );
2685
+ input.proxyB = b2MakeProxy( &explosionContext->position, 1, 0.0f );
2686
+ input.transformA = transform;
2687
+ input.transformB = b2Transform_identity;
2688
+ input.useRadii = true;
2689
+
2690
+ b2SimplexCache cache = { 0 };
2691
+ b2DistanceOutput output = b2ShapeDistance( &input, &cache, NULL, 0 );
2692
+
2693
+ float radius = explosionContext->radius;
2694
+ float falloff = explosionContext->falloff;
2695
+ if ( output.distance > radius + falloff )
2696
+ {
2697
+ return true;
2698
+ }
2699
+
2700
+ b2WakeBody( world, body );
2701
+
2702
+ if ( body->setIndex != b2_awakeSet )
2703
+ {
2704
+ return true;
2705
+ }
2706
+
2707
+ b2Vec2 closestPoint = output.pointA;
2708
+ if ( output.distance == 0.0f )
2709
+ {
2710
+ b2Vec2 localCentroid = b2GetShapeCentroid( shape );
2711
+ closestPoint = b2TransformPoint( transform, localCentroid );
2712
+ }
2713
+
2714
+ b2Vec2 direction = b2Sub( closestPoint, explosionContext->position );
2715
+ if ( b2LengthSquared( direction ) > 100.0f * FLT_EPSILON * FLT_EPSILON )
2716
+ {
2717
+ direction = b2Normalize( direction );
2718
+ }
2719
+ else
2720
+ {
2721
+ direction = (b2Vec2){ 1.0f, 0.0f };
2722
+ }
2723
+
2724
+ b2Vec2 localLine = b2InvRotateVector( transform.q, b2LeftPerp( direction ) );
2725
+ float perimeter = b2GetShapeProjectedPerimeter( shape, localLine );
2726
+ float scale = 1.0f;
2727
+ if ( output.distance > radius && falloff > 0.0f )
2728
+ {
2729
+ scale = b2ClampFloat( ( radius + falloff - output.distance ) / falloff, 0.0f, 1.0f );
2730
+ }
2731
+
2732
+ float magnitude = explosionContext->impulsePerLength * perimeter * scale;
2733
+ b2Vec2 impulse = b2MulSV( magnitude, direction );
2734
+
2735
+ int localIndex = body->localIndex;
2736
+ b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
2737
+ b2BodyState* state = b2BodyStateArray_Get( &set->bodyStates, localIndex );
2738
+ b2BodySim* bodySim = b2BodySimArray_Get( &set->bodySims, localIndex );
2739
+ state->linearVelocity = b2MulAdd( state->linearVelocity, bodySim->invMass, impulse );
2740
+ state->angularVelocity += bodySim->invInertia * b2Cross( b2Sub( closestPoint, bodySim->center ), impulse );
2741
+
2742
+ return true;
2743
+ }
2744
+
2745
+ void b2World_Explode( b2WorldId worldId, const b2ExplosionDef* explosionDef )
2746
+ {
2747
+ uint64_t maskBits = explosionDef->maskBits;
2748
+ b2Vec2 position = explosionDef->position;
2749
+ float radius = explosionDef->radius;
2750
+ float falloff = explosionDef->falloff;
2751
+ float impulsePerLength = explosionDef->impulsePerLength;
2752
+
2753
+ B2_ASSERT( b2IsValidVec2( position ) );
2754
+ B2_ASSERT( b2IsValidFloat( radius ) && radius >= 0.0f );
2755
+ B2_ASSERT( b2IsValidFloat( falloff ) && falloff >= 0.0f );
2756
+ B2_ASSERT( b2IsValidFloat( impulsePerLength ) );
2757
+
2758
+ b2World* world = b2GetWorldFromId( worldId );
2759
+ B2_ASSERT( world->locked == false );
2760
+ if ( world->locked )
2761
+ {
2762
+ return;
2763
+ }
2764
+
2765
+ struct ExplosionContext explosionContext = { world, position, radius, falloff, impulsePerLength };
2766
+
2767
+ b2AABB aabb;
2768
+ aabb.lowerBound.x = position.x - ( radius + falloff );
2769
+ aabb.lowerBound.y = position.y - ( radius + falloff );
2770
+ aabb.upperBound.x = position.x + ( radius + falloff );
2771
+ aabb.upperBound.y = position.y + ( radius + falloff );
2772
+
2773
+ b2DynamicTree_Query( world->broadPhase.trees + b2_dynamicBody, aabb, maskBits, ExplosionCallback, &explosionContext );
2774
+ }
2775
+
2776
+ void b2World_RebuildStaticTree( b2WorldId worldId )
2777
+ {
2778
+ b2World* world = b2GetWorldFromId( worldId );
2779
+ B2_ASSERT( world->locked == false );
2780
+ if ( world->locked )
2781
+ {
2782
+ return;
2783
+ }
2784
+
2785
+ b2DynamicTree* staticTree = world->broadPhase.trees + b2_staticBody;
2786
+ b2DynamicTree_Rebuild( staticTree, true );
2787
+ }
2788
+
2789
+ void b2World_EnableSpeculative( b2WorldId worldId, bool flag )
2790
+ {
2791
+ b2World* world = b2GetWorldFromId( worldId );
2792
+ world->enableSpeculative = flag;
2793
+ }
2794
+
2795
+ #if B2_VALIDATE
2796
+ // When validating islands ids I have to compare the root island
2797
+ // ids because islands are not merged until the next time step.
2798
+ static int b2GetRootIslandId( b2World* world, int islandId )
2799
+ {
2800
+ if ( islandId == B2_NULL_INDEX )
2801
+ {
2802
+ return B2_NULL_INDEX;
2803
+ }
2804
+
2805
+ b2Island* island = b2IslandArray_Get( &world->islands, islandId );
2806
+
2807
+ int rootId = islandId;
2808
+ b2Island* rootIsland = island;
2809
+ while ( rootIsland->parentIsland != B2_NULL_INDEX )
2810
+ {
2811
+ b2Island* parent = b2IslandArray_Get( &world->islands, rootIsland->parentIsland );
2812
+ rootId = rootIsland->parentIsland;
2813
+ rootIsland = parent;
2814
+ }
2815
+
2816
+ return rootId;
2817
+ }
2818
+
2819
+ // This validates island graph connectivity for each body
2820
+ void b2ValidateConnectivity( b2World* world )
2821
+ {
2822
+ b2Body* bodies = world->bodies.data;
2823
+ int bodyCapacity = world->bodies.count;
2824
+
2825
+ for ( int bodyIndex = 0; bodyIndex < bodyCapacity; ++bodyIndex )
2826
+ {
2827
+ b2Body* body = bodies + bodyIndex;
2828
+ if ( body->id == B2_NULL_INDEX )
2829
+ {
2830
+ b2ValidateFreeId( &world->bodyIdPool, bodyIndex );
2831
+ continue;
2832
+ }
2833
+
2834
+ b2ValidateUsedId( &world->bodyIdPool, bodyIndex );
2835
+
2836
+ B2_ASSERT( bodyIndex == body->id );
2837
+
2838
+ // Need to get the root island because islands are not merged until the next time step
2839
+ int bodyIslandId = b2GetRootIslandId( world, body->islandId );
2840
+ int bodySetIndex = body->setIndex;
2841
+
2842
+ int contactKey = body->headContactKey;
2843
+ while ( contactKey != B2_NULL_INDEX )
2844
+ {
2845
+ int contactId = contactKey >> 1;
2846
+ int edgeIndex = contactKey & 1;
2847
+
2848
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
2849
+
2850
+ bool touching = ( contact->flags & b2_contactTouchingFlag ) != 0;
2851
+ if ( touching )
2852
+ {
2853
+ if ( bodySetIndex != b2_staticSet )
2854
+ {
2855
+ int contactIslandId = b2GetRootIslandId( world, contact->islandId );
2856
+ B2_ASSERT( contactIslandId == bodyIslandId );
2857
+ }
2858
+ }
2859
+ else
2860
+ {
2861
+ B2_ASSERT( contact->islandId == B2_NULL_INDEX );
2862
+ }
2863
+
2864
+ contactKey = contact->edges[edgeIndex].nextKey;
2865
+ }
2866
+
2867
+ int jointKey = body->headJointKey;
2868
+ while ( jointKey != B2_NULL_INDEX )
2869
+ {
2870
+ int jointId = jointKey >> 1;
2871
+ int edgeIndex = jointKey & 1;
2872
+
2873
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointId );
2874
+
2875
+ int otherEdgeIndex = edgeIndex ^ 1;
2876
+
2877
+ b2Body* otherBody = b2BodyArray_Get( &world->bodies, joint->edges[otherEdgeIndex].bodyId );
2878
+
2879
+ if ( bodySetIndex == b2_disabledSet || otherBody->setIndex == b2_disabledSet )
2880
+ {
2881
+ B2_ASSERT( joint->islandId == B2_NULL_INDEX );
2882
+ }
2883
+ else if ( bodySetIndex == b2_staticSet )
2884
+ {
2885
+ if ( otherBody->setIndex == b2_staticSet )
2886
+ {
2887
+ B2_ASSERT( joint->islandId == B2_NULL_INDEX );
2888
+ }
2889
+ }
2890
+ else
2891
+ {
2892
+ int jointIslandId = b2GetRootIslandId( world, joint->islandId );
2893
+ B2_ASSERT( jointIslandId == bodyIslandId );
2894
+ }
2895
+
2896
+ jointKey = joint->edges[edgeIndex].nextKey;
2897
+ }
2898
+ }
2899
+ }
2900
+
2901
+ // Validates solver sets, but not island connectivity
2902
+ void b2ValidateSolverSets( b2World* world )
2903
+ {
2904
+ B2_ASSERT( b2GetIdCapacity( &world->bodyIdPool ) == world->bodies.count );
2905
+ B2_ASSERT( b2GetIdCapacity( &world->contactIdPool ) == world->contacts.count );
2906
+ B2_ASSERT( b2GetIdCapacity( &world->jointIdPool ) == world->joints.count );
2907
+ B2_ASSERT( b2GetIdCapacity( &world->islandIdPool ) == world->islands.count );
2908
+ B2_ASSERT( b2GetIdCapacity( &world->solverSetIdPool ) == world->solverSets.count );
2909
+
2910
+ int activeSetCount = 0;
2911
+ int totalBodyCount = 0;
2912
+ int totalJointCount = 0;
2913
+ int totalContactCount = 0;
2914
+ int totalIslandCount = 0;
2915
+
2916
+ // Validate all solver sets
2917
+ int setCount = world->solverSets.count;
2918
+ for ( int setIndex = 0; setIndex < setCount; ++setIndex )
2919
+ {
2920
+ b2SolverSet* set = world->solverSets.data + setIndex;
2921
+ if ( set->setIndex != B2_NULL_INDEX )
2922
+ {
2923
+ activeSetCount += 1;
2924
+
2925
+ if ( setIndex == b2_staticSet )
2926
+ {
2927
+ B2_ASSERT( set->contactSims.count == 0 );
2928
+ B2_ASSERT( set->islandSims.count == 0 );
2929
+ B2_ASSERT( set->bodyStates.count == 0 );
2930
+ }
2931
+ else if ( setIndex == b2_awakeSet )
2932
+ {
2933
+ B2_ASSERT( set->bodySims.count == set->bodyStates.count );
2934
+ B2_ASSERT( set->jointSims.count == 0 );
2935
+ }
2936
+ else if ( setIndex == b2_disabledSet )
2937
+ {
2938
+ B2_ASSERT( set->islandSims.count == 0 );
2939
+ B2_ASSERT( set->bodyStates.count == 0 );
2940
+ }
2941
+ else
2942
+ {
2943
+ B2_ASSERT( set->bodyStates.count == 0 );
2944
+ }
2945
+
2946
+ // Validate bodies
2947
+ {
2948
+ b2Body* bodies = world->bodies.data;
2949
+ B2_ASSERT( set->bodySims.count >= 0 );
2950
+ totalBodyCount += set->bodySims.count;
2951
+ for ( int i = 0; i < set->bodySims.count; ++i )
2952
+ {
2953
+ b2BodySim* bodySim = set->bodySims.data + i;
2954
+
2955
+ int bodyId = bodySim->bodyId;
2956
+ B2_ASSERT( 0 <= bodyId && bodyId < world->bodies.count );
2957
+ b2Body* body = bodies + bodyId;
2958
+ B2_ASSERT( body->setIndex == setIndex );
2959
+ B2_ASSERT( body->localIndex == i );
2960
+ B2_ASSERT( body->generation == body->generation );
2961
+
2962
+ if ( setIndex == b2_disabledSet )
2963
+ {
2964
+ B2_ASSERT( body->headContactKey == B2_NULL_INDEX );
2965
+ }
2966
+
2967
+ // Validate body shapes
2968
+ int prevShapeId = B2_NULL_INDEX;
2969
+ int shapeId = body->headShapeId;
2970
+ while ( shapeId != B2_NULL_INDEX )
2971
+ {
2972
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
2973
+ B2_ASSERT( shape->id == shapeId );
2974
+ B2_ASSERT( shape->prevShapeId == prevShapeId );
2975
+
2976
+ if ( setIndex == b2_disabledSet )
2977
+ {
2978
+ B2_ASSERT( shape->proxyKey == B2_NULL_INDEX );
2979
+ }
2980
+ else if ( setIndex == b2_staticSet )
2981
+ {
2982
+ B2_ASSERT( B2_PROXY_TYPE( shape->proxyKey ) == b2_staticBody );
2983
+ }
2984
+ else
2985
+ {
2986
+ b2BodyType proxyType = B2_PROXY_TYPE( shape->proxyKey );
2987
+ B2_ASSERT( proxyType == b2_kinematicBody || proxyType == b2_dynamicBody );
2988
+ }
2989
+
2990
+ prevShapeId = shapeId;
2991
+ shapeId = shape->nextShapeId;
2992
+ }
2993
+
2994
+ // Validate body contacts
2995
+ int contactKey = body->headContactKey;
2996
+ while ( contactKey != B2_NULL_INDEX )
2997
+ {
2998
+ int contactId = contactKey >> 1;
2999
+ int edgeIndex = contactKey & 1;
3000
+
3001
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId );
3002
+ B2_ASSERT( contact->setIndex != b2_staticSet );
3003
+ B2_ASSERT( contact->edges[0].bodyId == bodyId || contact->edges[1].bodyId == bodyId );
3004
+ contactKey = contact->edges[edgeIndex].nextKey;
3005
+ }
3006
+
3007
+ // Validate body joints
3008
+ int jointKey = body->headJointKey;
3009
+ while ( jointKey != B2_NULL_INDEX )
3010
+ {
3011
+ int jointId = jointKey >> 1;
3012
+ int edgeIndex = jointKey & 1;
3013
+
3014
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointId );
3015
+
3016
+ int otherEdgeIndex = edgeIndex ^ 1;
3017
+
3018
+ b2Body* otherBody = b2BodyArray_Get( &world->bodies, joint->edges[otherEdgeIndex].bodyId );
3019
+
3020
+ if ( setIndex == b2_disabledSet || otherBody->setIndex == b2_disabledSet )
3021
+ {
3022
+ B2_ASSERT( joint->setIndex == b2_disabledSet );
3023
+ }
3024
+ else if ( setIndex == b2_staticSet && otherBody->setIndex == b2_staticSet )
3025
+ {
3026
+ B2_ASSERT( joint->setIndex == b2_staticSet );
3027
+ }
3028
+ else if ( setIndex == b2_awakeSet )
3029
+ {
3030
+ B2_ASSERT( joint->setIndex == b2_awakeSet );
3031
+ }
3032
+ else if ( setIndex >= b2_firstSleepingSet )
3033
+ {
3034
+ B2_ASSERT( joint->setIndex == setIndex );
3035
+ }
3036
+
3037
+ b2JointSim* jointSim = b2GetJointSim( world, joint );
3038
+ B2_ASSERT( jointSim->jointId == jointId );
3039
+ B2_ASSERT( jointSim->bodyIdA == joint->edges[0].bodyId );
3040
+ B2_ASSERT( jointSim->bodyIdB == joint->edges[1].bodyId );
3041
+
3042
+ jointKey = joint->edges[edgeIndex].nextKey;
3043
+ }
3044
+ }
3045
+ }
3046
+
3047
+ // Validate contacts
3048
+ {
3049
+ B2_ASSERT( set->contactSims.count >= 0 );
3050
+ totalContactCount += set->contactSims.count;
3051
+ for ( int i = 0; i < set->contactSims.count; ++i )
3052
+ {
3053
+ b2ContactSim* contactSim = set->contactSims.data + i;
3054
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactSim->contactId );
3055
+ if ( setIndex == b2_awakeSet )
3056
+ {
3057
+ // contact should be non-touching if awake
3058
+ // or it could be this contact hasn't been transferred yet
3059
+ B2_ASSERT( contactSim->manifold.pointCount == 0 ||
3060
+ ( contactSim->simFlags & b2_simStartedTouching ) != 0 );
3061
+ }
3062
+ B2_ASSERT( contact->setIndex == setIndex );
3063
+ B2_ASSERT( contact->colorIndex == B2_NULL_INDEX );
3064
+ B2_ASSERT( contact->localIndex == i );
3065
+ }
3066
+ }
3067
+
3068
+ // Validate joints
3069
+ {
3070
+ B2_ASSERT( set->jointSims.count >= 0 );
3071
+ totalJointCount += set->jointSims.count;
3072
+ for ( int i = 0; i < set->jointSims.count; ++i )
3073
+ {
3074
+ b2JointSim* jointSim = set->jointSims.data + i;
3075
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointSim->jointId );
3076
+ B2_ASSERT( joint->setIndex == setIndex );
3077
+ B2_ASSERT( joint->colorIndex == B2_NULL_INDEX );
3078
+ B2_ASSERT( joint->localIndex == i );
3079
+ }
3080
+ }
3081
+
3082
+ // Validate islands
3083
+ {
3084
+ B2_ASSERT( set->islandSims.count >= 0 );
3085
+ totalIslandCount += set->islandSims.count;
3086
+ for ( int i = 0; i < set->islandSims.count; ++i )
3087
+ {
3088
+ b2IslandSim* islandSim = set->islandSims.data + i;
3089
+ b2Island* island = b2IslandArray_Get( &world->islands, islandSim->islandId );
3090
+ B2_ASSERT( island->setIndex == setIndex );
3091
+ B2_ASSERT( island->localIndex == i );
3092
+ }
3093
+ }
3094
+ }
3095
+ else
3096
+ {
3097
+ B2_ASSERT( set->bodySims.count == 0 );
3098
+ B2_ASSERT( set->contactSims.count == 0 );
3099
+ B2_ASSERT( set->jointSims.count == 0 );
3100
+ B2_ASSERT( set->islandSims.count == 0 );
3101
+ B2_ASSERT( set->bodyStates.count == 0 );
3102
+ }
3103
+ }
3104
+
3105
+ int setIdCount = b2GetIdCount( &world->solverSetIdPool );
3106
+ B2_ASSERT( activeSetCount == setIdCount );
3107
+
3108
+ int bodyIdCount = b2GetIdCount( &world->bodyIdPool );
3109
+ B2_ASSERT( totalBodyCount == bodyIdCount );
3110
+
3111
+ int islandIdCount = b2GetIdCount( &world->islandIdPool );
3112
+ B2_ASSERT( totalIslandCount == islandIdCount );
3113
+
3114
+ // Validate constraint graph
3115
+ for ( int colorIndex = 0; colorIndex < B2_GRAPH_COLOR_COUNT; ++colorIndex )
3116
+ {
3117
+ b2GraphColor* color = world->constraintGraph.colors + colorIndex;
3118
+ {
3119
+ B2_ASSERT( color->contactSims.count >= 0 );
3120
+ totalContactCount += color->contactSims.count;
3121
+ for ( int i = 0; i < color->contactSims.count; ++i )
3122
+ {
3123
+ b2ContactSim* contactSim = color->contactSims.data + i;
3124
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactSim->contactId );
3125
+ // contact should be touching in the constraint graph or awaiting transfer to non-touching
3126
+ B2_ASSERT( contactSim->manifold.pointCount > 0 ||
3127
+ ( contactSim->simFlags & ( b2_simStoppedTouching | b2_simDisjoint ) ) != 0 );
3128
+ B2_ASSERT( contact->setIndex == b2_awakeSet );
3129
+ B2_ASSERT( contact->colorIndex == colorIndex );
3130
+ B2_ASSERT( contact->localIndex == i );
3131
+
3132
+ int bodyIdA = contact->edges[0].bodyId;
3133
+ int bodyIdB = contact->edges[1].bodyId;
3134
+
3135
+ if ( colorIndex < B2_OVERFLOW_INDEX )
3136
+ {
3137
+ b2Body* bodyA = b2BodyArray_Get( &world->bodies, bodyIdA );
3138
+ b2Body* bodyB = b2BodyArray_Get( &world->bodies, bodyIdB );
3139
+ B2_ASSERT( b2GetBit( &color->bodySet, bodyIdA ) == ( bodyA->type != b2_staticBody ) );
3140
+ B2_ASSERT( b2GetBit( &color->bodySet, bodyIdB ) == ( bodyB->type != b2_staticBody ) );
3141
+ }
3142
+ }
3143
+ }
3144
+
3145
+ {
3146
+ B2_ASSERT( color->jointSims.count >= 0 );
3147
+ totalJointCount += color->jointSims.count;
3148
+ for ( int i = 0; i < color->jointSims.count; ++i )
3149
+ {
3150
+ b2JointSim* jointSim = color->jointSims.data + i;
3151
+ b2Joint* joint = b2JointArray_Get( &world->joints, jointSim->jointId );
3152
+ B2_ASSERT( joint->setIndex == b2_awakeSet );
3153
+ B2_ASSERT( joint->colorIndex == colorIndex );
3154
+ B2_ASSERT( joint->localIndex == i );
3155
+
3156
+ int bodyIdA = joint->edges[0].bodyId;
3157
+ int bodyIdB = joint->edges[1].bodyId;
3158
+
3159
+ if ( colorIndex < B2_OVERFLOW_INDEX )
3160
+ {
3161
+ b2Body* bodyA = b2BodyArray_Get( &world->bodies, bodyIdA );
3162
+ b2Body* bodyB = b2BodyArray_Get( &world->bodies, bodyIdB );
3163
+ B2_ASSERT( b2GetBit( &color->bodySet, bodyIdA ) == ( bodyA->type != b2_staticBody ) );
3164
+ B2_ASSERT( b2GetBit( &color->bodySet, bodyIdB ) == ( bodyB->type != b2_staticBody ) );
3165
+ }
3166
+ }
3167
+ }
3168
+ }
3169
+
3170
+ int contactIdCount = b2GetIdCount( &world->contactIdPool );
3171
+ B2_ASSERT( totalContactCount == contactIdCount );
3172
+ B2_ASSERT( totalContactCount == (int)world->broadPhase.pairSet.count );
3173
+
3174
+ int jointIdCount = b2GetIdCount( &world->jointIdPool );
3175
+ B2_ASSERT( totalJointCount == jointIdCount );
3176
+
3177
+ // Validate shapes
3178
+ // This is very slow on compounds
3179
+ #if 0
3180
+ int shapeCapacity = b2Array(world->shapeArray).count;
3181
+ for (int shapeIndex = 0; shapeIndex < shapeCapacity; shapeIndex += 1)
3182
+ {
3183
+ b2Shape* shape = world->shapeArray + shapeIndex;
3184
+ if (shape->id != shapeIndex)
3185
+ {
3186
+ continue;
3187
+ }
3188
+
3189
+ B2_ASSERT(0 <= shape->bodyId && shape->bodyId < b2Array(world->bodyArray).count);
3190
+
3191
+ b2Body* body = world->bodyArray + shape->bodyId;
3192
+ B2_ASSERT(0 <= body->setIndex && body->setIndex < b2Array(world->solverSetArray).count);
3193
+
3194
+ b2SolverSet* set = world->solverSetArray + body->setIndex;
3195
+ B2_ASSERT(0 <= body->localIndex && body->localIndex < set->sims.count);
3196
+
3197
+ b2BodySim* bodySim = set->sims.data + body->localIndex;
3198
+ B2_ASSERT(bodySim->bodyId == shape->bodyId);
3199
+
3200
+ bool found = false;
3201
+ int shapeCount = 0;
3202
+ int index = body->headShapeId;
3203
+ while (index != B2_NULL_INDEX)
3204
+ {
3205
+ b2CheckId(world->shapeArray, index);
3206
+ b2Shape* s = world->shapeArray + index;
3207
+ if (index == shapeIndex)
3208
+ {
3209
+ found = true;
3210
+ }
3211
+
3212
+ index = s->nextShapeId;
3213
+ shapeCount += 1;
3214
+ }
3215
+
3216
+ B2_ASSERT(found);
3217
+ B2_ASSERT(shapeCount == body->shapeCount);
3218
+ }
3219
+ #endif
3220
+ }
3221
+
3222
+ // Validate contact touching status.
3223
+ void b2ValidateContacts( b2World* world )
3224
+ {
3225
+ int contactCount = world->contacts.count;
3226
+ B2_ASSERT( contactCount == b2GetIdCapacity( &world->contactIdPool ) );
3227
+ int allocatedContactCount = 0;
3228
+
3229
+ for ( int contactIndex = 0; contactIndex < contactCount; ++contactIndex )
3230
+ {
3231
+ b2Contact* contact = b2ContactArray_Get( &world->contacts, contactIndex );
3232
+ if ( contact->contactId == B2_NULL_INDEX )
3233
+ {
3234
+ continue;
3235
+ }
3236
+
3237
+ B2_ASSERT( contact->contactId == contactIndex );
3238
+
3239
+ allocatedContactCount += 1;
3240
+
3241
+ bool touching = ( contact->flags & b2_contactTouchingFlag ) != 0;
3242
+
3243
+ int setId = contact->setIndex;
3244
+
3245
+ if ( setId == b2_awakeSet )
3246
+ {
3247
+ // If touching and not a sensor
3248
+ if ( touching )
3249
+ {
3250
+ B2_ASSERT( 0 <= contact->colorIndex && contact->colorIndex < B2_GRAPH_COLOR_COUNT );
3251
+ }
3252
+ else
3253
+ {
3254
+ B2_ASSERT( contact->colorIndex == B2_NULL_INDEX );
3255
+ }
3256
+ }
3257
+ else if ( setId >= b2_firstSleepingSet )
3258
+ {
3259
+ // Only touching contacts allowed in a sleeping set
3260
+ B2_ASSERT( touching == true );
3261
+ }
3262
+ else
3263
+ {
3264
+ // Sleeping and non-touching contacts or sensor contacts belong in the disabled set
3265
+ B2_ASSERT( touching == false && setId == b2_disabledSet );
3266
+ }
3267
+
3268
+ b2ContactSim* contactSim = b2GetContactSim( world, contact );
3269
+ B2_ASSERT( contactSim->contactId == contactIndex );
3270
+ B2_ASSERT( contactSim->bodyIdA == contact->edges[0].bodyId );
3271
+ B2_ASSERT( contactSim->bodyIdB == contact->edges[1].bodyId );
3272
+
3273
+ // Sim touching is true for solid and sensor contacts
3274
+ bool simTouching = ( contactSim->simFlags & b2_simTouchingFlag ) != 0;
3275
+ B2_ASSERT( touching == simTouching );
3276
+
3277
+ B2_ASSERT( 0 <= contactSim->manifold.pointCount && contactSim->manifold.pointCount <= 2 );
3278
+ }
3279
+
3280
+ int contactIdCount = b2GetIdCount( &world->contactIdPool );
3281
+ B2_ASSERT( allocatedContactCount == contactIdCount );
3282
+ }
3283
+
3284
+ #else
3285
+
3286
+ void b2ValidateConnectivity( b2World* world )
3287
+ {
3288
+ B2_UNUSED( world );
3289
+ }
3290
+
3291
+ void b2ValidateSolverSets( b2World* world )
3292
+ {
3293
+ B2_UNUSED( world );
3294
+ }
3295
+
3296
+ void b2ValidateContacts( b2World* world )
3297
+ {
3298
+ B2_UNUSED( world );
3299
+ }
3300
+
3301
+ #endif