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,2038 @@
1
+ // SPDX-FileCopyrightText: 2023 Erin Catto
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ #include "solver.h"
5
+
6
+ #include "array.h"
7
+ #include "atomic.h"
8
+ #include "bitset.h"
9
+ #include "body.h"
10
+ #include "contact.h"
11
+ #include "contact_solver.h"
12
+ #include "core.h"
13
+ #include "ctz.h"
14
+ #include "island.h"
15
+ #include "joint.h"
16
+ #include "shape.h"
17
+ #include "solver_set.h"
18
+ #include "arena_allocator.h"
19
+ #include "world.h"
20
+
21
+ #include <limits.h>
22
+ #include <stdbool.h>
23
+ #include <stddef.h>
24
+
25
+ // Compare to SDL_CPUPauseInstruction
26
+ #if ( defined( __GNUC__ ) || defined( __clang__ ) ) && ( defined( __i386__ ) || defined( __x86_64__ ) )
27
+ static inline void b2Pause( void )
28
+ {
29
+ __asm__ __volatile__( "pause\n" );
30
+ }
31
+ #elif ( defined( __arm__ ) && defined( __ARM_ARCH ) && __ARM_ARCH >= 7 ) || defined( __aarch64__ )
32
+ static inline void b2Pause( void )
33
+ {
34
+ __asm__ __volatile__( "yield" ::: "memory" );
35
+ }
36
+ #elif defined( _MSC_VER ) && ( defined( _M_IX86 ) || defined( _M_X64 ) )
37
+ //#include <immintrin.h>
38
+ static inline void b2Pause( void )
39
+ {
40
+ _mm_pause();
41
+ }
42
+ #elif defined( _MSC_VER ) && ( defined( _M_ARM ) || defined( _M_ARM64 ) )
43
+ static inline void b2Pause( void )
44
+ {
45
+ __yield();
46
+ }
47
+ #else
48
+ static inline void b2Pause( void )
49
+ {
50
+ }
51
+ #endif
52
+
53
+ typedef struct b2WorkerContext
54
+ {
55
+ b2StepContext* context;
56
+ int workerIndex;
57
+ void* userTask;
58
+ } b2WorkerContext;
59
+
60
+ // Integrate velocities and apply damping
61
+ static void b2IntegrateVelocitiesTask( int startIndex, int endIndex, b2StepContext* context )
62
+ {
63
+ b2TracyCZoneNC( integrate_velocity, "IntVel", b2_colorDeepPink, true );
64
+
65
+ b2BodyState* states = context->states;
66
+ b2BodySim* sims = context->sims;
67
+
68
+ b2Vec2 gravity = context->world->gravity;
69
+ float h = context->h;
70
+ float maxLinearSpeed = context->maxLinearVelocity;
71
+ float maxAngularSpeed = B2_MAX_ROTATION * context->inv_dt;
72
+ float maxLinearSpeedSquared = maxLinearSpeed * maxLinearSpeed;
73
+ float maxAngularSpeedSquared = maxAngularSpeed * maxAngularSpeed;
74
+
75
+ for ( int i = startIndex; i < endIndex; ++i )
76
+ {
77
+ b2BodySim* sim = sims + i;
78
+ b2BodyState* state = states + i;
79
+
80
+ b2Vec2 v = state->linearVelocity;
81
+ float w = state->angularVelocity;
82
+
83
+ // Apply forces, torque, gravity, and damping
84
+ // Apply damping.
85
+ // Differential equation: dv/dt + c * v = 0
86
+ // Solution: v(t) = v0 * exp(-c * t)
87
+ // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v(t) * exp(-c * dt)
88
+ // v2 = exp(-c * dt) * v1
89
+ // Pade approximation:
90
+ // v2 = v1 * 1 / (1 + c * dt)
91
+ float linearDamping = 1.0f / ( 1.0f + h * sim->linearDamping );
92
+ float angularDamping = 1.0f / ( 1.0f + h * sim->angularDamping );
93
+
94
+ // Gravity scale will be zero for kinematic bodies
95
+ float gravityScale = sim->invMass > 0.0f ? sim->gravityScale : 0.0f;
96
+
97
+ // lvd = h * im * f + h * g
98
+ b2Vec2 linearVelocityDelta = b2Add( b2MulSV( h * sim->invMass, sim->force ), b2MulSV( h * gravityScale, gravity ) );
99
+ float angularVelocityDelta = h * sim->invInertia * sim->torque;
100
+
101
+ v = b2MulAdd( linearVelocityDelta, linearDamping, v );
102
+ w = angularVelocityDelta + angularDamping * w;
103
+
104
+ // Clamp to max linear speed
105
+ if ( b2Dot( v, v ) > maxLinearSpeedSquared )
106
+ {
107
+ float ratio = maxLinearSpeed / b2Length( v );
108
+ v = b2MulSV( ratio, v );
109
+ sim->isSpeedCapped = true;
110
+ }
111
+
112
+ // Clamp to max angular speed
113
+ if ( w * w > maxAngularSpeedSquared && sim->allowFastRotation == false )
114
+ {
115
+ float ratio = maxAngularSpeed / b2AbsFloat( w );
116
+ w *= ratio;
117
+ sim->isSpeedCapped = true;
118
+ }
119
+
120
+ state->linearVelocity = v;
121
+ state->angularVelocity = w;
122
+ }
123
+
124
+ b2TracyCZoneEnd( integrate_velocity );
125
+ }
126
+
127
+ static void b2PrepareJointsTask( int startIndex, int endIndex, b2StepContext* context )
128
+ {
129
+ b2TracyCZoneNC( prepare_joints, "PrepJoints", b2_colorOldLace, true );
130
+
131
+ b2JointSim** joints = context->joints;
132
+
133
+ for ( int i = startIndex; i < endIndex; ++i )
134
+ {
135
+ b2JointSim* joint = joints[i];
136
+ b2PrepareJoint( joint, context );
137
+ }
138
+
139
+ b2TracyCZoneEnd( prepare_joints );
140
+ }
141
+
142
+ static void b2WarmStartJointsTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex )
143
+ {
144
+ b2TracyCZoneNC( warm_joints, "WarmJoints", b2_colorGold, true );
145
+
146
+ b2GraphColor* color = context->graph->colors + colorIndex;
147
+ b2JointSim* joints = color->jointSims.data;
148
+ B2_ASSERT( 0 <= startIndex && startIndex < color->jointSims.count );
149
+ B2_ASSERT( startIndex <= endIndex && endIndex <= color->jointSims.count );
150
+
151
+ for ( int i = startIndex; i < endIndex; ++i )
152
+ {
153
+ b2JointSim* joint = joints + i;
154
+ b2WarmStartJoint( joint, context );
155
+ }
156
+
157
+ b2TracyCZoneEnd( warm_joints );
158
+ }
159
+
160
+ static void b2SolveJointsTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex, bool useBias )
161
+ {
162
+ b2TracyCZoneNC( solve_joints, "SolveJoints", b2_colorLemonChiffon, true );
163
+
164
+ b2GraphColor* color = context->graph->colors + colorIndex;
165
+ b2JointSim* joints = color->jointSims.data;
166
+ B2_ASSERT( 0 <= startIndex && startIndex < color->jointSims.count );
167
+ B2_ASSERT( startIndex <= endIndex && endIndex <= color->jointSims.count );
168
+
169
+ for ( int i = startIndex; i < endIndex; ++i )
170
+ {
171
+ b2JointSim* joint = joints + i;
172
+ b2SolveJoint( joint, context, useBias );
173
+ }
174
+
175
+ b2TracyCZoneEnd( solve_joints );
176
+ }
177
+
178
+ static void b2IntegratePositionsTask( int startIndex, int endIndex, b2StepContext* context )
179
+ {
180
+ b2TracyCZoneNC( integrate_positions, "IntPos", b2_colorDarkSeaGreen, true );
181
+
182
+ b2BodyState* states = context->states;
183
+ float h = context->h;
184
+
185
+ B2_ASSERT( startIndex <= endIndex );
186
+
187
+ for ( int i = startIndex; i < endIndex; ++i )
188
+ {
189
+ b2BodyState* state = states + i;
190
+ state->deltaRotation = b2IntegrateRotation( state->deltaRotation, h * state->angularVelocity );
191
+ state->deltaPosition = b2MulAdd( state->deltaPosition, h, state->linearVelocity );
192
+ }
193
+
194
+ b2TracyCZoneEnd( integrate_positions );
195
+ }
196
+
197
+ struct b2ContinuousContext
198
+ {
199
+ b2World* world;
200
+ b2BodySim* fastBodySim;
201
+ b2Shape* fastShape;
202
+ b2Vec2 centroid1, centroid2;
203
+ b2Sweep sweep;
204
+ float fraction;
205
+ };
206
+
207
+ // This is called from b2DynamicTree_Query for continuous collision
208
+ static bool b2ContinuousQueryCallback( int proxyId, uint64_t userData, void* context )
209
+ {
210
+ B2_UNUSED( proxyId );
211
+
212
+ int shapeId = (int)userData;
213
+
214
+ struct b2ContinuousContext* continuousContext = context;
215
+ b2Shape* fastShape = continuousContext->fastShape;
216
+ b2BodySim* fastBodySim = continuousContext->fastBodySim;
217
+
218
+ // Skip same shape
219
+ if ( shapeId == fastShape->id )
220
+ {
221
+ return true;
222
+ }
223
+
224
+ b2World* world = continuousContext->world;
225
+
226
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
227
+
228
+ // Skip same body
229
+ if ( shape->bodyId == fastShape->bodyId )
230
+ {
231
+ return true;
232
+ }
233
+
234
+ // Skip sensors
235
+ if ( shape->sensorIndex != B2_NULL_INDEX )
236
+ {
237
+ return true;
238
+ }
239
+
240
+ // Skip filtered shapes
241
+ bool canCollide = b2ShouldShapesCollide( fastShape->filter, shape->filter );
242
+ if ( canCollide == false )
243
+ {
244
+ return true;
245
+ }
246
+
247
+ b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId );
248
+
249
+ b2BodySim* bodySim = b2GetBodySim( world, body );
250
+ B2_ASSERT( body->type == b2_staticBody || fastBodySim->isBullet );
251
+
252
+ // Skip bullets
253
+ if ( bodySim->isBullet )
254
+ {
255
+ return true;
256
+ }
257
+
258
+ // Skip filtered bodies
259
+ b2Body* fastBody = b2BodyArray_Get( &world->bodies, fastBodySim->bodyId );
260
+ canCollide = b2ShouldBodiesCollide( world, fastBody, body );
261
+ if ( canCollide == false )
262
+ {
263
+ return true;
264
+ }
265
+
266
+ // Custom user filtering
267
+ b2CustomFilterFcn* customFilterFcn = world->customFilterFcn;
268
+ if ( customFilterFcn != NULL )
269
+ {
270
+ b2ShapeId idA = { shape->id + 1, world->worldId, shape->generation };
271
+ b2ShapeId idB = { fastShape->id + 1, world->worldId, fastShape->generation };
272
+ canCollide = customFilterFcn( idA, idB, world->customFilterContext );
273
+ if ( canCollide == false )
274
+ {
275
+ return true;
276
+ }
277
+ }
278
+
279
+ // Prevent pausing on chain segment junctions
280
+ if ( shape->type == b2_chainSegmentShape )
281
+ {
282
+ b2Transform transform = bodySim->transform;
283
+ b2Vec2 p1 = b2TransformPoint( transform, shape->chainSegment.segment.point1 );
284
+ b2Vec2 p2 = b2TransformPoint( transform, shape->chainSegment.segment.point2 );
285
+ b2Vec2 e = b2Sub( p2, p1 );
286
+ float length;
287
+ e = b2GetLengthAndNormalize( &length, e );
288
+ if (length > B2_LINEAR_SLOP)
289
+ {
290
+ b2Vec2 c1 = continuousContext->centroid1;
291
+ float offset1 = b2Cross( b2Sub( c1, p1 ), e );
292
+ b2Vec2 c2 = continuousContext->centroid2;
293
+ float offset2 = b2Cross( b2Sub( c2, p1 ), e );
294
+
295
+ // todo this should use the min extent of the fast shape, not the body
296
+ const float allowedFraction = 0.25f;
297
+ if ( offset1 < 0.0f || offset1 - offset2 < allowedFraction * fastBodySim->minExtent )
298
+ {
299
+ // Minimal clipping
300
+ return true;
301
+ }
302
+ }
303
+ }
304
+
305
+ // todo_erin testing early out for segments
306
+ #if 0
307
+ if ( shape->type == b2_segmentShape )
308
+ {
309
+ b2Transform transform = bodySim->transform;
310
+ b2Vec2 p1 = b2TransformPoint( transform, shape->segment.point1 );
311
+ b2Vec2 p2 = b2TransformPoint( transform, shape->segment.point2 );
312
+ b2Vec2 e = b2Sub( p2, p1 );
313
+ b2Vec2 c1 = continuousContext->centroid1;
314
+ b2Vec2 c2 = continuousContext->centroid2;
315
+ float offset1 = b2Cross( b2Sub( c1, p1 ), e );
316
+ float offset2 = b2Cross( b2Sub( c2, p1 ), e );
317
+
318
+ if ( offset1 > 0.0f && offset2 > 0.0f )
319
+ {
320
+ // Started behind or finished in front
321
+ return true;
322
+ }
323
+
324
+ if ( offset1 < 0.0f && offset2 < 0.0f )
325
+ {
326
+ // Started behind or finished in front
327
+ return true;
328
+ }
329
+ }
330
+ #endif
331
+
332
+ b2TOIInput input;
333
+ input.proxyA = b2MakeShapeDistanceProxy( shape );
334
+ input.proxyB = b2MakeShapeDistanceProxy( fastShape );
335
+ input.sweepA = b2MakeSweep( bodySim );
336
+ input.sweepB = continuousContext->sweep;
337
+ input.maxFraction = continuousContext->fraction;
338
+
339
+ float hitFraction = continuousContext->fraction;
340
+
341
+ bool didHit = false;
342
+ b2TOIOutput output = b2TimeOfImpact( &input );
343
+ if ( 0.0f < output.fraction && output.fraction < continuousContext->fraction )
344
+ {
345
+ hitFraction = output.fraction;
346
+ didHit = true;
347
+ }
348
+ else if ( 0.0f == output.fraction )
349
+ {
350
+ // fallback to TOI of a small circle around the fast shape centroid
351
+ b2Vec2 centroid = b2GetShapeCentroid( fastShape );
352
+ b2ShapeExtent extent = b2ComputeShapeExtent( fastShape, centroid );
353
+ float radius = 0.25f * extent.minExtent;
354
+ input.proxyB = b2MakeProxy( &centroid, 1, radius );
355
+ output = b2TimeOfImpact( &input );
356
+ if ( 0.0f < output.fraction && output.fraction < continuousContext->fraction )
357
+ {
358
+ hitFraction = output.fraction;
359
+ didHit = true;
360
+ }
361
+ }
362
+
363
+ if ( didHit && ( shape->enablePreSolveEvents || fastShape->enablePreSolveEvents ) )
364
+ {
365
+ // Pre-solve is expensive because I need to compute a temporary manifold
366
+ b2Transform transformA = b2GetSweepTransform( &input.sweepA, hitFraction );
367
+ b2Transform transformB = b2GetSweepTransform( &input.sweepB, hitFraction );
368
+ b2Manifold manifold = b2ComputeManifold( shape, transformA, fastShape, transformB );
369
+ b2ShapeId shapeIdA = { shape->id + 1, world->worldId, shape->generation };
370
+ b2ShapeId shapeIdB = { fastShape->id + 1, world->worldId, fastShape->generation };
371
+
372
+ // The user may modify the temporary manifold here but it doesn't matter. They will be able to
373
+ // modify the real manifold in the discrete solver.
374
+ didHit = world->preSolveFcn( shapeIdA, shapeIdB, &manifold, world->preSolveContext );
375
+ }
376
+
377
+ if ( didHit )
378
+ {
379
+ continuousContext->fraction = hitFraction;
380
+ }
381
+
382
+ return true;
383
+ }
384
+
385
+ // Continuous collision of dynamic versus static
386
+ static void b2SolveContinuous( b2World* world, int bodySimIndex )
387
+ {
388
+ b2TracyCZoneNC( ccd, "CCD", b2_colorDarkGoldenRod, true );
389
+
390
+ b2SolverSet* awakeSet = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
391
+ b2BodySim* fastBodySim = b2BodySimArray_Get( &awakeSet->bodySims, bodySimIndex );
392
+ B2_ASSERT( fastBodySim->isFast );
393
+
394
+ b2Sweep sweep = b2MakeSweep( fastBodySim );
395
+
396
+ b2Transform xf1;
397
+ xf1.q = sweep.q1;
398
+ xf1.p = b2Sub( sweep.c1, b2RotateVector( sweep.q1, sweep.localCenter ) );
399
+
400
+ b2Transform xf2;
401
+ xf2.q = sweep.q2;
402
+ xf2.p = b2Sub( sweep.c2, b2RotateVector( sweep.q2, sweep.localCenter ) );
403
+
404
+ b2DynamicTree* staticTree = world->broadPhase.trees + b2_staticBody;
405
+ b2DynamicTree* kinematicTree = world->broadPhase.trees + b2_kinematicBody;
406
+ b2DynamicTree* dynamicTree = world->broadPhase.trees + b2_dynamicBody;
407
+ b2Body* fastBody = b2BodyArray_Get( &world->bodies, fastBodySim->bodyId );
408
+
409
+ struct b2ContinuousContext context;
410
+ context.world = world;
411
+ context.sweep = sweep;
412
+ context.fastBodySim = fastBodySim;
413
+ context.fraction = 1.0f;
414
+
415
+ bool isBullet = fastBodySim->isBullet;
416
+
417
+ int shapeId = fastBody->headShapeId;
418
+ while ( shapeId != B2_NULL_INDEX )
419
+ {
420
+ b2Shape* fastShape = b2ShapeArray_Get( &world->shapes, shapeId );
421
+ shapeId = fastShape->nextShapeId;
422
+
423
+ context.fastShape = fastShape;
424
+ context.centroid1 = b2TransformPoint( xf1, fastShape->localCentroid );
425
+ context.centroid2 = b2TransformPoint( xf2, fastShape->localCentroid );
426
+
427
+ b2AABB box1 = fastShape->aabb;
428
+ b2AABB box2 = b2ComputeShapeAABB( fastShape, xf2 );
429
+ b2AABB box = b2AABB_Union( box1, box2 );
430
+
431
+ // Store this to avoid double computation in the case there is no impact event
432
+ fastShape->aabb = box2;
433
+
434
+ // No continuous collision for sensors (but still need the updated bounds)
435
+ if ( fastShape->sensorIndex != B2_NULL_INDEX )
436
+ {
437
+ continue;
438
+ }
439
+
440
+ b2DynamicTree_Query( staticTree, box, B2_DEFAULT_MASK_BITS, b2ContinuousQueryCallback, &context );
441
+
442
+ if ( isBullet )
443
+ {
444
+ b2DynamicTree_Query( kinematicTree, box, B2_DEFAULT_MASK_BITS, b2ContinuousQueryCallback, &context );
445
+ b2DynamicTree_Query( dynamicTree, box, B2_DEFAULT_MASK_BITS, b2ContinuousQueryCallback, &context );
446
+ }
447
+ }
448
+
449
+ const float speculativeDistance = B2_SPECULATIVE_DISTANCE;
450
+ const float aabbMargin = B2_AABB_MARGIN;
451
+
452
+ if ( context.fraction < 1.0f )
453
+ {
454
+ // Handle time of impact event
455
+ b2Rot q = b2NLerp( sweep.q1, sweep.q2, context.fraction );
456
+ b2Vec2 c = b2Lerp( sweep.c1, sweep.c2, context.fraction );
457
+ b2Vec2 origin = b2Sub( c, b2RotateVector( q, sweep.localCenter ) );
458
+
459
+ // Advance body
460
+ b2Transform transform = { origin, q };
461
+ fastBodySim->transform = transform;
462
+ fastBodySim->center = c;
463
+ fastBodySim->rotation0 = q;
464
+ fastBodySim->center0 = c;
465
+
466
+ // Update body move event
467
+ b2BodyMoveEvent* event = b2BodyMoveEventArray_Get( &world->bodyMoveEvents, bodySimIndex );
468
+ event->transform = transform;
469
+
470
+ // Prepare AABBs for broad-phase.
471
+ // Even though a body is fast, it may not move much. So the
472
+ // AABB may not need enlargement.
473
+
474
+ shapeId = fastBody->headShapeId;
475
+ while ( shapeId != B2_NULL_INDEX )
476
+ {
477
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
478
+
479
+ // Must recompute aabb at the interpolated transform
480
+ b2AABB aabb = b2ComputeShapeAABB( shape, transform );
481
+ aabb.lowerBound.x -= speculativeDistance;
482
+ aabb.lowerBound.y -= speculativeDistance;
483
+ aabb.upperBound.x += speculativeDistance;
484
+ aabb.upperBound.y += speculativeDistance;
485
+ shape->aabb = aabb;
486
+
487
+ if ( b2AABB_Contains( shape->fatAABB, aabb ) == false )
488
+ {
489
+ b2AABB fatAABB;
490
+ fatAABB.lowerBound.x = aabb.lowerBound.x - aabbMargin;
491
+ fatAABB.lowerBound.y = aabb.lowerBound.y - aabbMargin;
492
+ fatAABB.upperBound.x = aabb.upperBound.x + aabbMargin;
493
+ fatAABB.upperBound.y = aabb.upperBound.y + aabbMargin;
494
+ shape->fatAABB = fatAABB;
495
+
496
+ shape->enlargedAABB = true;
497
+ fastBodySim->enlargeAABB = true;
498
+ }
499
+
500
+ shapeId = shape->nextShapeId;
501
+ }
502
+ }
503
+ else
504
+ {
505
+ // No time of impact event
506
+
507
+ // Advance body
508
+ fastBodySim->rotation0 = fastBodySim->transform.q;
509
+ fastBodySim->center0 = fastBodySim->center;
510
+
511
+ // Prepare AABBs for broad-phase
512
+ shapeId = fastBody->headShapeId;
513
+ while ( shapeId != B2_NULL_INDEX )
514
+ {
515
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
516
+
517
+ // shape->aabb is still valid from above
518
+
519
+ if ( b2AABB_Contains( shape->fatAABB, shape->aabb ) == false )
520
+ {
521
+ b2AABB fatAABB;
522
+ fatAABB.lowerBound.x = shape->aabb.lowerBound.x - aabbMargin;
523
+ fatAABB.lowerBound.y = shape->aabb.lowerBound.y - aabbMargin;
524
+ fatAABB.upperBound.x = shape->aabb.upperBound.x + aabbMargin;
525
+ fatAABB.upperBound.y = shape->aabb.upperBound.y + aabbMargin;
526
+ shape->fatAABB = fatAABB;
527
+
528
+ shape->enlargedAABB = true;
529
+ fastBodySim->enlargeAABB = true;
530
+ }
531
+
532
+ shapeId = shape->nextShapeId;
533
+ }
534
+ }
535
+
536
+ b2TracyCZoneEnd( ccd );
537
+ }
538
+
539
+ static void b2FinalizeBodiesTask( int startIndex, int endIndex, uint32_t threadIndex, void* context )
540
+ {
541
+ b2TracyCZoneNC( finalize_transfprms, "Transforms", b2_colorMediumSeaGreen, true );
542
+
543
+ b2StepContext* stepContext = context;
544
+ b2World* world = stepContext->world;
545
+ bool enableSleep = world->enableSleep;
546
+ b2BodyState* states = stepContext->states;
547
+ b2BodySim* sims = stepContext->sims;
548
+ b2Body* bodies = world->bodies.data;
549
+ float timeStep = stepContext->dt;
550
+ float invTimeStep = stepContext->inv_dt;
551
+
552
+ uint16_t worldId = world->worldId;
553
+
554
+ // The body move event array should already have the correct size
555
+ B2_ASSERT( endIndex <= world->bodyMoveEvents.count );
556
+ b2BodyMoveEvent* moveEvents = world->bodyMoveEvents.data;
557
+
558
+ b2BitSet* enlargedSimBitSet = &world->taskContexts.data[threadIndex].enlargedSimBitSet;
559
+ b2BitSet* awakeIslandBitSet = &world->taskContexts.data[threadIndex].awakeIslandBitSet;
560
+ b2TaskContext* taskContext = world->taskContexts.data + threadIndex;
561
+
562
+ bool enableContinuous = world->enableContinuous;
563
+
564
+ const float speculativeDistance = B2_SPECULATIVE_DISTANCE;
565
+ const float aabbMargin = B2_AABB_MARGIN;
566
+
567
+ B2_ASSERT( startIndex <= endIndex );
568
+
569
+ for ( int simIndex = startIndex; simIndex < endIndex; ++simIndex )
570
+ {
571
+ b2BodyState* state = states + simIndex;
572
+ b2BodySim* sim = sims + simIndex;
573
+
574
+ b2Vec2 v = state->linearVelocity;
575
+ float w = state->angularVelocity;
576
+
577
+ B2_ASSERT( b2IsValidVec2( v ) );
578
+ B2_ASSERT( b2IsValidFloat( w ) );
579
+
580
+ sim->center = b2Add( sim->center, state->deltaPosition );
581
+ sim->transform.q = b2NormalizeRot( b2MulRot( state->deltaRotation, sim->transform.q ) );
582
+
583
+ // Use the velocity of the farthest point on the body to account for rotation.
584
+ float maxVelocity = b2Length( v ) + b2AbsFloat( w ) * sim->maxExtent;
585
+
586
+ // Sleep needs to observe position correction as well as true velocity.
587
+ float maxDeltaPosition = b2Length( state->deltaPosition ) + b2AbsFloat( state->deltaRotation.s ) * sim->maxExtent;
588
+
589
+ // Position correction is not as important for sleep as true velocity.
590
+ float positionSleepFactor = 0.5f;
591
+
592
+ float sleepVelocity = b2MaxFloat( maxVelocity, positionSleepFactor * invTimeStep * maxDeltaPosition );
593
+
594
+ // reset state deltas
595
+ state->deltaPosition = b2Vec2_zero;
596
+ state->deltaRotation = b2Rot_identity;
597
+
598
+ sim->transform.p = b2Sub( sim->center, b2RotateVector( sim->transform.q, sim->localCenter ) );
599
+
600
+ // cache miss here, however I need the shape list below
601
+ b2Body* body = bodies + sim->bodyId;
602
+ body->bodyMoveIndex = simIndex;
603
+ moveEvents[simIndex].transform = sim->transform;
604
+ moveEvents[simIndex].bodyId = ( b2BodyId ){ sim->bodyId + 1, worldId, body->generation };
605
+ moveEvents[simIndex].userData = body->userData;
606
+ moveEvents[simIndex].fellAsleep = false;
607
+
608
+ // reset applied force and torque
609
+ sim->force = b2Vec2_zero;
610
+ sim->torque = 0.0f;
611
+
612
+ body->isSpeedCapped = sim->isSpeedCapped;
613
+ sim->isSpeedCapped = false;
614
+
615
+ sim->isFast = false;
616
+
617
+ if ( enableSleep == false || body->enableSleep == false || sleepVelocity > body->sleepThreshold )
618
+ {
619
+ // Body is not sleepy
620
+ body->sleepTime = 0.0f;
621
+
622
+ if ( body->type == b2_dynamicBody && enableContinuous && maxVelocity * timeStep > 0.5f * sim->minExtent )
623
+ {
624
+ // This flag is only retained for debug draw
625
+ sim->isFast = true;
626
+
627
+ // Store in fast array for the continuous collision stage
628
+ // This is deterministic because the order of TOI sweeps doesn't matter
629
+ if ( sim->isBullet )
630
+ {
631
+ int bulletIndex = b2AtomicFetchAddInt( &stepContext->bulletBodyCount, 1 );
632
+ stepContext->bulletBodies[bulletIndex] = simIndex;
633
+ }
634
+ else
635
+ {
636
+ b2SolveContinuous( world, simIndex );
637
+ }
638
+ }
639
+ else
640
+ {
641
+ // Body is safe to advance
642
+ sim->center0 = sim->center;
643
+ sim->rotation0 = sim->transform.q;
644
+ }
645
+ }
646
+ else
647
+ {
648
+ // Body is safe to advance and is falling asleep
649
+ sim->center0 = sim->center;
650
+ sim->rotation0 = sim->transform.q;
651
+ body->sleepTime += timeStep;
652
+ }
653
+
654
+ // Any single body in an island can keep it awake
655
+ b2Island* island = b2IslandArray_Get( &world->islands, body->islandId );
656
+ if ( body->sleepTime < B2_TIME_TO_SLEEP )
657
+ {
658
+ // keep island awake
659
+ int islandIndex = island->localIndex;
660
+ b2SetBit( awakeIslandBitSet, islandIndex );
661
+ }
662
+ else if ( island->constraintRemoveCount > 0 )
663
+ {
664
+ // body wants to sleep but its island needs splitting first
665
+ if ( body->sleepTime > taskContext->splitSleepTime )
666
+ {
667
+ // pick the sleepiest candidate
668
+ taskContext->splitIslandId = body->islandId;
669
+ taskContext->splitSleepTime = body->sleepTime;
670
+ }
671
+ }
672
+
673
+ // Update shapes AABBs
674
+ b2Transform transform = sim->transform;
675
+ bool isFast = sim->isFast;
676
+ int shapeId = body->headShapeId;
677
+ while ( shapeId != B2_NULL_INDEX )
678
+ {
679
+ b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId );
680
+
681
+ if ( isFast )
682
+ {
683
+ // For fast non-bullet bodies the AABB has already been updated in b2SolveContinuous
684
+ // For fast bullet bodies the AABB will be updated at a later stage
685
+
686
+ // Add to enlarged shapes regardless of AABB changes.
687
+ // Bit-set to keep the move array sorted
688
+ b2SetBit( enlargedSimBitSet, simIndex );
689
+ }
690
+ else
691
+ {
692
+ b2AABB aabb = b2ComputeShapeAABB( shape, transform );
693
+ aabb.lowerBound.x -= speculativeDistance;
694
+ aabb.lowerBound.y -= speculativeDistance;
695
+ aabb.upperBound.x += speculativeDistance;
696
+ aabb.upperBound.y += speculativeDistance;
697
+ shape->aabb = aabb;
698
+
699
+ B2_ASSERT( shape->enlargedAABB == false );
700
+
701
+ if ( b2AABB_Contains( shape->fatAABB, aabb ) == false )
702
+ {
703
+ b2AABB fatAABB;
704
+ fatAABB.lowerBound.x = aabb.lowerBound.x - aabbMargin;
705
+ fatAABB.lowerBound.y = aabb.lowerBound.y - aabbMargin;
706
+ fatAABB.upperBound.x = aabb.upperBound.x + aabbMargin;
707
+ fatAABB.upperBound.y = aabb.upperBound.y + aabbMargin;
708
+ shape->fatAABB = fatAABB;
709
+
710
+ shape->enlargedAABB = true;
711
+
712
+ // Bit-set to keep the move array sorted
713
+ b2SetBit( enlargedSimBitSet, simIndex );
714
+ }
715
+ }
716
+
717
+ shapeId = shape->nextShapeId;
718
+ }
719
+ }
720
+
721
+ b2TracyCZoneEnd( finalize_transfprms );
722
+ }
723
+
724
+ /*
725
+ typedef enum b2SolverStageType
726
+ {
727
+ b2_stagePrepareJoints,
728
+ b2_stagePrepareContacts,
729
+ b2_stageIntegrateVelocities,
730
+ b2_stageWarmStart,
731
+ b2_stageSolve,
732
+ b2_stageIntegratePositions,
733
+ b2_stageRelax,
734
+ b2_stageRestitution,
735
+ b2_stageStoreImpulses
736
+ } b2SolverStageType;
737
+
738
+ typedef enum b2SolverBlockType
739
+ {
740
+ b2_bodyBlock,
741
+ b2_jointBlock,
742
+ b2_contactBlock,
743
+ b2_graphJointBlock,
744
+ b2_graphContactBlock
745
+ } b2SolverBlockType;
746
+ */
747
+
748
+ static void b2ExecuteBlock( b2SolverStage* stage, b2StepContext* context, b2SolverBlock* block )
749
+ {
750
+ b2SolverStageType stageType = stage->type;
751
+ b2SolverBlockType blockType = block->blockType;
752
+ int startIndex = block->startIndex;
753
+ int endIndex = startIndex + block->count;
754
+
755
+ switch ( stageType )
756
+ {
757
+ case b2_stagePrepareJoints:
758
+ b2PrepareJointsTask( startIndex, endIndex, context );
759
+ break;
760
+
761
+ case b2_stagePrepareContacts:
762
+ b2PrepareContactsTask( startIndex, endIndex, context );
763
+ break;
764
+
765
+ case b2_stageIntegrateVelocities:
766
+ b2IntegrateVelocitiesTask( startIndex, endIndex, context );
767
+ break;
768
+
769
+ case b2_stageWarmStart:
770
+ if ( context->world->enableWarmStarting )
771
+ {
772
+ if ( blockType == b2_graphContactBlock )
773
+ {
774
+ b2WarmStartContactsTask( startIndex, endIndex, context, stage->colorIndex );
775
+ }
776
+ else if ( blockType == b2_graphJointBlock )
777
+ {
778
+ b2WarmStartJointsTask( startIndex, endIndex, context, stage->colorIndex );
779
+ }
780
+ }
781
+ break;
782
+
783
+ case b2_stageSolve:
784
+ if ( blockType == b2_graphContactBlock )
785
+ {
786
+ b2SolveContactsTask( startIndex, endIndex, context, stage->colorIndex, true );
787
+ }
788
+ else if ( blockType == b2_graphJointBlock )
789
+ {
790
+ b2SolveJointsTask( startIndex, endIndex, context, stage->colorIndex, true );
791
+ }
792
+ break;
793
+
794
+ case b2_stageIntegratePositions:
795
+ b2IntegratePositionsTask( startIndex, endIndex, context );
796
+ break;
797
+
798
+ case b2_stageRelax:
799
+ if ( blockType == b2_graphContactBlock )
800
+ {
801
+ b2SolveContactsTask( startIndex, endIndex, context, stage->colorIndex, false );
802
+ }
803
+ else if ( blockType == b2_graphJointBlock )
804
+ {
805
+ b2SolveJointsTask( startIndex, endIndex, context, stage->colorIndex, false );
806
+ }
807
+ break;
808
+
809
+ case b2_stageRestitution:
810
+ if ( blockType == b2_graphContactBlock )
811
+ {
812
+ b2ApplyRestitutionTask( startIndex, endIndex, context, stage->colorIndex );
813
+ }
814
+ break;
815
+
816
+ case b2_stageStoreImpulses:
817
+ b2StoreImpulsesTask( startIndex, endIndex, context );
818
+ break;
819
+ }
820
+ }
821
+
822
+ static inline int GetWorkerStartIndex( int workerIndex, int blockCount, int workerCount )
823
+ {
824
+ if ( blockCount <= workerCount )
825
+ {
826
+ return workerIndex < blockCount ? workerIndex : B2_NULL_INDEX;
827
+ }
828
+
829
+ int blocksPerWorker = blockCount / workerCount;
830
+ int remainder = blockCount - blocksPerWorker * workerCount;
831
+ return blocksPerWorker * workerIndex + b2MinInt( remainder, workerIndex );
832
+ }
833
+
834
+ static void b2ExecuteStage( b2SolverStage* stage, b2StepContext* context, int previousSyncIndex, int syncIndex, int workerIndex )
835
+ {
836
+ int completedCount = 0;
837
+ b2SolverBlock* blocks = stage->blocks;
838
+ int blockCount = stage->blockCount;
839
+
840
+ int expectedSyncIndex = previousSyncIndex;
841
+
842
+ int startIndex = GetWorkerStartIndex( workerIndex, blockCount, context->workerCount );
843
+ if ( startIndex == B2_NULL_INDEX )
844
+ {
845
+ return;
846
+ }
847
+
848
+ B2_ASSERT( 0 <= startIndex && startIndex < blockCount );
849
+
850
+ int blockIndex = startIndex;
851
+
852
+ while ( b2AtomicCompareExchangeInt( &blocks[blockIndex].syncIndex, expectedSyncIndex, syncIndex ) == true )
853
+ {
854
+ B2_ASSERT( stage->type != b2_stagePrepareContacts || syncIndex < 2 );
855
+
856
+ B2_ASSERT( completedCount < blockCount );
857
+
858
+ b2ExecuteBlock( stage, context, blocks + blockIndex );
859
+
860
+ completedCount += 1;
861
+ blockIndex += 1;
862
+ if ( blockIndex >= blockCount )
863
+ {
864
+ // Keep looking for work
865
+ blockIndex = 0;
866
+ }
867
+
868
+ expectedSyncIndex = previousSyncIndex;
869
+ }
870
+
871
+ // Search backwards for blocks
872
+ blockIndex = startIndex - 1;
873
+ while ( true )
874
+ {
875
+ if ( blockIndex < 0 )
876
+ {
877
+ blockIndex = blockCount - 1;
878
+ }
879
+
880
+ expectedSyncIndex = previousSyncIndex;
881
+
882
+ if ( b2AtomicCompareExchangeInt( &blocks[blockIndex].syncIndex, expectedSyncIndex, syncIndex ) == false )
883
+ {
884
+ break;
885
+ }
886
+
887
+ b2ExecuteBlock( stage, context, blocks + blockIndex );
888
+ completedCount += 1;
889
+ blockIndex -= 1;
890
+ }
891
+
892
+ (void)b2AtomicFetchAddInt( &stage->completionCount, completedCount );
893
+ }
894
+
895
+ static void b2ExecuteMainStage( b2SolverStage* stage, b2StepContext* context, uint32_t syncBits )
896
+ {
897
+ int blockCount = stage->blockCount;
898
+ if ( blockCount == 0 )
899
+ {
900
+ return;
901
+ }
902
+
903
+ if ( blockCount == 1 )
904
+ {
905
+ b2ExecuteBlock( stage, context, stage->blocks );
906
+ }
907
+ else
908
+ {
909
+ b2AtomicStoreU32( &context->atomicSyncBits, syncBits );
910
+
911
+ int syncIndex = ( syncBits >> 16 ) & 0xFFFF;
912
+ B2_ASSERT( syncIndex > 0 );
913
+ int previousSyncIndex = syncIndex - 1;
914
+
915
+ b2ExecuteStage( stage, context, previousSyncIndex, syncIndex, 0 );
916
+
917
+ // todo consider using the cycle counter as well
918
+ while ( b2AtomicLoadInt( &stage->completionCount ) != blockCount )
919
+ {
920
+ b2Pause();
921
+ }
922
+
923
+ b2AtomicStoreInt( &stage->completionCount, 0 );
924
+ }
925
+ }
926
+
927
+ // This should not use the thread index because thread 0 can be called twice by enkiTS.
928
+ static void b2SolverTask( int startIndex, int endIndex, uint32_t threadIndexIgnore, void* taskContext )
929
+ {
930
+ B2_UNUSED( startIndex, endIndex, threadIndexIgnore );
931
+
932
+ b2WorkerContext* workerContext = taskContext;
933
+ int workerIndex = workerContext->workerIndex;
934
+ b2StepContext* context = workerContext->context;
935
+ int activeColorCount = context->activeColorCount;
936
+ b2SolverStage* stages = context->stages;
937
+ b2Profile* profile = &context->world->profile;
938
+
939
+ if ( workerIndex == 0 )
940
+ {
941
+ // Main thread synchronizes the workers and does work itself.
942
+ //
943
+ // Stages are re-used by loops so that I don't need more stages for large iteration counts.
944
+ // The sync indices grow monotonically for the body/graph/constraint groupings because they share solver blocks.
945
+ // The stage index and sync indices are combined in to sync bits for atomic synchronization.
946
+ // The workers need to compute the previous sync index for a given stage so that CAS works correctly. This
947
+ // setup makes this easy to do.
948
+
949
+ /*
950
+ b2_stagePrepareJoints,
951
+ b2_stagePrepareContacts,
952
+ b2_stageIntegrateVelocities,
953
+ b2_stageWarmStart,
954
+ b2_stageSolve,
955
+ b2_stageIntegratePositions,
956
+ b2_stageRelax,
957
+ b2_stageRestitution,
958
+ b2_stageStoreImpulses
959
+ */
960
+
961
+ uint64_t ticks = b2GetTicks();
962
+
963
+ int bodySyncIndex = 1;
964
+ int stageIndex = 0;
965
+
966
+ // This stage loops over all awake joints
967
+ uint32_t jointSyncIndex = 1;
968
+ uint32_t syncBits = ( jointSyncIndex << 16 ) | stageIndex;
969
+ B2_ASSERT( stages[stageIndex].type == b2_stagePrepareJoints );
970
+ b2ExecuteMainStage( stages + stageIndex, context, syncBits );
971
+ stageIndex += 1;
972
+ jointSyncIndex += 1;
973
+
974
+ // This stage loops over all contact constraints
975
+ uint32_t contactSyncIndex = 1;
976
+ syncBits = ( contactSyncIndex << 16 ) | stageIndex;
977
+ B2_ASSERT( stages[stageIndex].type == b2_stagePrepareContacts );
978
+ b2ExecuteMainStage( stages + stageIndex, context, syncBits );
979
+ stageIndex += 1;
980
+ contactSyncIndex += 1;
981
+
982
+ int graphSyncIndex = 1;
983
+
984
+ // Single-threaded overflow work. These constraints don't fit in the graph coloring.
985
+ b2PrepareOverflowJoints( context );
986
+ b2PrepareOverflowContacts( context );
987
+
988
+ profile->prepareConstraints += b2GetMillisecondsAndReset( &ticks );
989
+
990
+ int subStepCount = context->subStepCount;
991
+ for ( int i = 0; i < subStepCount; ++i )
992
+ {
993
+ // stage index restarted each iteration
994
+ // syncBits still increases monotonically because the upper bits increase each iteration
995
+ int iterStageIndex = stageIndex;
996
+
997
+ // integrate velocities
998
+ syncBits = ( bodySyncIndex << 16 ) | iterStageIndex;
999
+ B2_ASSERT( stages[iterStageIndex].type == b2_stageIntegrateVelocities );
1000
+ b2ExecuteMainStage( stages + iterStageIndex, context, syncBits );
1001
+ iterStageIndex += 1;
1002
+ bodySyncIndex += 1;
1003
+
1004
+ profile->integrateVelocities += b2GetMillisecondsAndReset( &ticks );
1005
+
1006
+ // warm start constraints
1007
+ b2WarmStartOverflowJoints( context );
1008
+ b2WarmStartOverflowContacts( context );
1009
+
1010
+ for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex )
1011
+ {
1012
+ syncBits = ( graphSyncIndex << 16 ) | iterStageIndex;
1013
+ B2_ASSERT( stages[iterStageIndex].type == b2_stageWarmStart );
1014
+ b2ExecuteMainStage( stages + iterStageIndex, context, syncBits );
1015
+ iterStageIndex += 1;
1016
+ }
1017
+ graphSyncIndex += 1;
1018
+
1019
+ profile->warmStart += b2GetMillisecondsAndReset( &ticks );
1020
+
1021
+ // solve constraints
1022
+ bool useBias = true;
1023
+ b2SolveOverflowJoints( context, useBias );
1024
+ b2SolveOverflowContacts( context, useBias );
1025
+
1026
+ for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex )
1027
+ {
1028
+ syncBits = ( graphSyncIndex << 16 ) | iterStageIndex;
1029
+ B2_ASSERT( stages[iterStageIndex].type == b2_stageSolve );
1030
+ b2ExecuteMainStage( stages + iterStageIndex, context, syncBits );
1031
+ iterStageIndex += 1;
1032
+ }
1033
+ graphSyncIndex += 1;
1034
+
1035
+ profile->solveImpulses += b2GetMillisecondsAndReset( &ticks );
1036
+
1037
+ // integrate positions
1038
+ B2_ASSERT( stages[iterStageIndex].type == b2_stageIntegratePositions );
1039
+ syncBits = ( bodySyncIndex << 16 ) | iterStageIndex;
1040
+ b2ExecuteMainStage( stages + iterStageIndex, context, syncBits );
1041
+ iterStageIndex += 1;
1042
+ bodySyncIndex += 1;
1043
+
1044
+ profile->integratePositions += b2GetMillisecondsAndReset( &ticks );
1045
+
1046
+ // relax constraints
1047
+ useBias = false;
1048
+ b2SolveOverflowJoints( context, useBias );
1049
+ b2SolveOverflowContacts( context, useBias );
1050
+
1051
+ for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex )
1052
+ {
1053
+ syncBits = ( graphSyncIndex << 16 ) | iterStageIndex;
1054
+ B2_ASSERT( stages[iterStageIndex].type == b2_stageRelax );
1055
+ b2ExecuteMainStage( stages + iterStageIndex, context, syncBits );
1056
+ iterStageIndex += 1;
1057
+ }
1058
+ graphSyncIndex += 1;
1059
+
1060
+ profile->relaxImpulses += b2GetMillisecondsAndReset( &ticks );
1061
+ }
1062
+
1063
+ // advance the stage according to the sub-stepping tasks just completed
1064
+ // integrate velocities / warm start / solve / integrate positions / relax
1065
+ stageIndex += 1 + activeColorCount + activeColorCount + 1 + activeColorCount;
1066
+
1067
+ // Restitution
1068
+ {
1069
+ b2ApplyOverflowRestitution( context );
1070
+
1071
+ int iterStageIndex = stageIndex;
1072
+ for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex )
1073
+ {
1074
+ syncBits = ( graphSyncIndex << 16 ) | iterStageIndex;
1075
+ B2_ASSERT( stages[iterStageIndex].type == b2_stageRestitution );
1076
+ b2ExecuteMainStage( stages + iterStageIndex, context, syncBits );
1077
+ iterStageIndex += 1;
1078
+ }
1079
+ // graphSyncIndex += 1;
1080
+ stageIndex += activeColorCount;
1081
+ }
1082
+
1083
+ profile->applyRestitution += b2GetMillisecondsAndReset( &ticks );
1084
+
1085
+ b2StoreOverflowImpulses( context );
1086
+
1087
+ syncBits = ( contactSyncIndex << 16 ) | stageIndex;
1088
+ B2_ASSERT( stages[stageIndex].type == b2_stageStoreImpulses );
1089
+ b2ExecuteMainStage( stages + stageIndex, context, syncBits );
1090
+
1091
+ profile->storeImpulses += b2GetMillisecondsAndReset( &ticks );
1092
+
1093
+ // Signal workers to finish
1094
+ b2AtomicStoreU32( &context->atomicSyncBits, UINT_MAX );
1095
+
1096
+ B2_ASSERT( stageIndex + 1 == context->stageCount );
1097
+ return;
1098
+ }
1099
+
1100
+ // Worker spins and waits for work
1101
+ uint32_t lastSyncBits = 0;
1102
+ // uint64_t maxSpinTime = 10;
1103
+ while ( true )
1104
+ {
1105
+ // Spin until main thread bumps changes the sync bits. This can waste significant time overall, but it is necessary for
1106
+ // parallel simulation with graph coloring.
1107
+ uint32_t syncBits;
1108
+ int spinCount = 0;
1109
+ while ( ( syncBits = b2AtomicLoadU32( &context->atomicSyncBits ) ) == lastSyncBits )
1110
+ {
1111
+ if ( spinCount > 5 )
1112
+ {
1113
+ b2Yield();
1114
+ spinCount = 0;
1115
+ }
1116
+ else
1117
+ {
1118
+ // Using the cycle counter helps to account for variation in mm_pause timing across different
1119
+ // CPUs. However, this is X64 only.
1120
+ // uint64_t prev = __rdtsc();
1121
+ // do
1122
+ //{
1123
+ // b2Pause();
1124
+ //}
1125
+ // while ((__rdtsc() - prev) < maxSpinTime);
1126
+ // maxSpinTime += 10;
1127
+ b2Pause();
1128
+ b2Pause();
1129
+ spinCount += 1;
1130
+ }
1131
+ }
1132
+
1133
+ if ( syncBits == UINT_MAX )
1134
+ {
1135
+ // sentinel hit
1136
+ break;
1137
+ }
1138
+
1139
+ int stageIndex = syncBits & 0xFFFF;
1140
+ B2_ASSERT( stageIndex < context->stageCount );
1141
+
1142
+ int syncIndex = ( syncBits >> 16 ) & 0xFFFF;
1143
+ B2_ASSERT( syncIndex > 0 );
1144
+
1145
+ int previousSyncIndex = syncIndex - 1;
1146
+
1147
+ b2SolverStage* stage = stages + stageIndex;
1148
+ b2ExecuteStage( stage, context, previousSyncIndex, syncIndex, workerIndex );
1149
+
1150
+ lastSyncBits = syncBits;
1151
+ }
1152
+ }
1153
+
1154
+ static void b2BulletBodyTask( int startIndex, int endIndex, uint32_t threadIndex, void* taskContext )
1155
+ {
1156
+ B2_UNUSED( threadIndex );
1157
+
1158
+ b2TracyCZoneNC( bullet_body_task, "Bullet", b2_colorLightSkyBlue, true );
1159
+
1160
+ b2StepContext* stepContext = taskContext;
1161
+
1162
+ B2_ASSERT( startIndex <= endIndex );
1163
+
1164
+ for ( int i = startIndex; i < endIndex; ++i )
1165
+ {
1166
+ int simIndex = stepContext->bulletBodies[i];
1167
+ b2SolveContinuous( stepContext->world, simIndex );
1168
+ }
1169
+
1170
+ b2TracyCZoneEnd( bullet_body_task );
1171
+ }
1172
+
1173
+ #if B2_SIMD_WIDTH == 8
1174
+ #define B2_SIMD_SHIFT 3
1175
+ #elif B2_SIMD_WIDTH == 4
1176
+ #define B2_SIMD_SHIFT 2
1177
+ #else
1178
+ #define B2_SIMD_SHIFT 0
1179
+ #endif
1180
+
1181
+ // Solve with graph coloring
1182
+ void b2Solve( b2World* world, b2StepContext* stepContext )
1183
+ {
1184
+ world->stepIndex += 1;
1185
+
1186
+ // Merge islands
1187
+ {
1188
+ b2TracyCZoneNC( merge, "Merge", b2_colorLightGoldenRodYellow, true );
1189
+ uint64_t mergeTicks = b2GetTicks();
1190
+
1191
+ b2MergeAwakeIslands( world );
1192
+
1193
+ world->profile.mergeIslands = b2GetMilliseconds( mergeTicks );
1194
+ b2TracyCZoneEnd( merge );
1195
+ }
1196
+
1197
+ // Are there any awake bodies? This scenario should not be important for profiling.
1198
+ b2SolverSet* awakeSet = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet );
1199
+ int awakeBodyCount = awakeSet->bodySims.count;
1200
+ if ( awakeBodyCount == 0 )
1201
+ {
1202
+ // Nothing to simulate, however the tree rebuild must be finished.
1203
+ if ( world->userTreeTask != NULL )
1204
+ {
1205
+ world->finishTaskFcn( world->userTreeTask, world->userTaskContext );
1206
+ world->userTreeTask = NULL;
1207
+ world->activeTaskCount -= 1;
1208
+ }
1209
+
1210
+ b2ValidateNoEnlarged( &world->broadPhase );
1211
+ return;
1212
+ }
1213
+
1214
+ // Solve constraints using graph coloring
1215
+ {
1216
+ // Prepare buffers for bullets
1217
+ b2AtomicStoreInt(&stepContext->bulletBodyCount, 0);
1218
+ stepContext->bulletBodies = b2AllocateArenaItem( &world->arena, awakeBodyCount * sizeof( int ), "bullet bodies" );
1219
+
1220
+ b2TracyCZoneNC( prepare_stages, "Prepare Stages", b2_colorDarkOrange, true );
1221
+ uint64_t prepareTicks = b2GetTicks();
1222
+
1223
+ b2ConstraintGraph* graph = &world->constraintGraph;
1224
+ b2GraphColor* colors = graph->colors;
1225
+
1226
+ stepContext->sims = awakeSet->bodySims.data;
1227
+ stepContext->states = awakeSet->bodyStates.data;
1228
+
1229
+ // count contacts, joints, and colors
1230
+ int awakeJointCount = 0;
1231
+ int activeColorCount = 0;
1232
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT - 1; ++i )
1233
+ {
1234
+ int perColorContactCount = colors[i].contactSims.count;
1235
+ int perColorJointCount = colors[i].jointSims.count;
1236
+ int occupancyCount = perColorContactCount + perColorJointCount;
1237
+ activeColorCount += occupancyCount > 0 ? 1 : 0;
1238
+ awakeJointCount += perColorJointCount;
1239
+ }
1240
+
1241
+ // prepare for move events
1242
+ b2BodyMoveEventArray_Resize( &world->bodyMoveEvents, awakeBodyCount );
1243
+
1244
+ // Each worker receives at most M blocks of work. The workers may receive less blocks if there is not sufficient work.
1245
+ // Each block of work has a minimum number of elements (block size). This in turn may limit the number of blocks.
1246
+ // If there are many elements then the block size is increased so there are still at most M blocks of work per worker.
1247
+ // M is a tunable number that has two goals:
1248
+ // 1. keep M small to reduce overhead
1249
+ // 2. keep M large enough for other workers to be able to steal work
1250
+ // The block size is a power of two to make math efficient.
1251
+
1252
+ int workerCount = world->workerCount;
1253
+ const int blocksPerWorker = 4;
1254
+ const int maxBlockCount = blocksPerWorker * workerCount;
1255
+
1256
+ // Configure blocks for tasks that parallel-for bodies
1257
+ int bodyBlockSize = 1 << 5;
1258
+ int bodyBlockCount;
1259
+ if ( awakeBodyCount > bodyBlockSize * maxBlockCount )
1260
+ {
1261
+ // Too many blocks, increase block size
1262
+ bodyBlockSize = awakeBodyCount / maxBlockCount;
1263
+ bodyBlockCount = maxBlockCount;
1264
+ }
1265
+ else
1266
+ {
1267
+ bodyBlockCount = ( ( awakeBodyCount - 1 ) >> 5 ) + 1;
1268
+ }
1269
+
1270
+ // Configure blocks for tasks parallel-for each active graph color
1271
+ // The blocks are a mix of SIMD contact blocks and joint blocks
1272
+ int activeColorIndices[B2_GRAPH_COLOR_COUNT];
1273
+
1274
+ int colorContactCounts[B2_GRAPH_COLOR_COUNT];
1275
+ int colorContactBlockSizes[B2_GRAPH_COLOR_COUNT];
1276
+ int colorContactBlockCounts[B2_GRAPH_COLOR_COUNT];
1277
+
1278
+ int colorJointCounts[B2_GRAPH_COLOR_COUNT];
1279
+ int colorJointBlockSizes[B2_GRAPH_COLOR_COUNT];
1280
+ int colorJointBlockCounts[B2_GRAPH_COLOR_COUNT];
1281
+
1282
+ int graphBlockCount = 0;
1283
+
1284
+ // c is the active color index
1285
+ int simdContactCount = 0;
1286
+ int c = 0;
1287
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT - 1; ++i )
1288
+ {
1289
+ int colorContactCount = colors[i].contactSims.count;
1290
+ int colorJointCount = colors[i].jointSims.count;
1291
+
1292
+ if ( colorContactCount + colorJointCount > 0 )
1293
+ {
1294
+ activeColorIndices[c] = i;
1295
+
1296
+ // 4/8-way SIMD
1297
+ int colorContactCountSIMD = colorContactCount > 0 ? ( ( colorContactCount - 1 ) >> B2_SIMD_SHIFT ) + 1 : 0;
1298
+
1299
+ colorContactCounts[c] = colorContactCountSIMD;
1300
+
1301
+ // determine the number of contact work blocks for this color
1302
+ if ( colorContactCountSIMD > blocksPerWorker * maxBlockCount )
1303
+ {
1304
+ // too many contact blocks
1305
+ colorContactBlockSizes[c] = colorContactCountSIMD / maxBlockCount;
1306
+ colorContactBlockCounts[c] = maxBlockCount;
1307
+ }
1308
+ else if ( colorContactCountSIMD > 0 )
1309
+ {
1310
+ // dividing by blocksPerWorker (4)
1311
+ colorContactBlockSizes[c] = blocksPerWorker;
1312
+ colorContactBlockCounts[c] = ( ( colorContactCountSIMD - 1 ) >> 2 ) + 1;
1313
+ }
1314
+ else
1315
+ {
1316
+ // no contacts in this color
1317
+ colorContactBlockSizes[c] = 0;
1318
+ colorContactBlockCounts[c] = 0;
1319
+ }
1320
+
1321
+ colorJointCounts[c] = colorJointCount;
1322
+
1323
+ // determine number of joint work blocks for this color
1324
+ if ( colorJointCount > blocksPerWorker * maxBlockCount )
1325
+ {
1326
+ // too many joint blocks
1327
+ colorJointBlockSizes[c] = colorJointCount / maxBlockCount;
1328
+ colorJointBlockCounts[c] = maxBlockCount;
1329
+ }
1330
+ else if ( colorJointCount > 0 )
1331
+ {
1332
+ // dividing by blocksPerWorker (4)
1333
+ colorJointBlockSizes[c] = blocksPerWorker;
1334
+ colorJointBlockCounts[c] = ( ( colorJointCount - 1 ) >> 2 ) + 1;
1335
+ }
1336
+ else
1337
+ {
1338
+ colorJointBlockSizes[c] = 0;
1339
+ colorJointBlockCounts[c] = 0;
1340
+ }
1341
+
1342
+ graphBlockCount += colorContactBlockCounts[c] + colorJointBlockCounts[c];
1343
+ simdContactCount += colorContactCountSIMD;
1344
+ c += 1;
1345
+ }
1346
+ }
1347
+ activeColorCount = c;
1348
+
1349
+ // Gather contact pointers for easy parallel-for traversal. Some may be NULL due to SIMD remainders.
1350
+ b2ContactSim** contacts = b2AllocateArenaItem(
1351
+ &world->arena, B2_SIMD_WIDTH * simdContactCount * sizeof( b2ContactSim* ), "contact pointers" );
1352
+
1353
+ // Gather joint pointers for easy parallel-for traversal.
1354
+ b2JointSim** joints =
1355
+ b2AllocateArenaItem( &world->arena, awakeJointCount * sizeof( b2JointSim* ), "joint pointers" );
1356
+
1357
+ int simdConstraintSize = b2GetContactConstraintSIMDByteCount();
1358
+ b2ContactConstraintSIMD* simdContactConstraints =
1359
+ b2AllocateArenaItem( &world->arena, simdContactCount * simdConstraintSize, "contact constraint" );
1360
+
1361
+ int overflowContactCount = colors[B2_OVERFLOW_INDEX].contactSims.count;
1362
+ b2ContactConstraint* overflowContactConstraints = b2AllocateArenaItem(
1363
+ &world->arena, overflowContactCount * sizeof( b2ContactConstraint ), "overflow contact constraint" );
1364
+
1365
+ graph->colors[B2_OVERFLOW_INDEX].overflowConstraints = overflowContactConstraints;
1366
+
1367
+ // Distribute transient constraints to each graph color and build flat arrays of contact and joint pointers
1368
+ {
1369
+ int contactBase = 0;
1370
+ int jointBase = 0;
1371
+ for ( int i = 0; i < activeColorCount; ++i )
1372
+ {
1373
+ int j = activeColorIndices[i];
1374
+ b2GraphColor* color = colors + j;
1375
+
1376
+ int colorContactCount = color->contactSims.count;
1377
+
1378
+ if ( colorContactCount == 0 )
1379
+ {
1380
+ color->simdConstraints = NULL;
1381
+ }
1382
+ else
1383
+ {
1384
+ color->simdConstraints =
1385
+ (b2ContactConstraintSIMD*)( (uint8_t*)simdContactConstraints + contactBase * simdConstraintSize );
1386
+
1387
+ for ( int k = 0; k < colorContactCount; ++k )
1388
+ {
1389
+ contacts[B2_SIMD_WIDTH * contactBase + k] = color->contactSims.data + k;
1390
+ }
1391
+
1392
+ // remainder
1393
+ int colorContactCountSIMD = ( ( colorContactCount - 1 ) >> B2_SIMD_SHIFT ) + 1;
1394
+ for ( int k = colorContactCount; k < B2_SIMD_WIDTH * colorContactCountSIMD; ++k )
1395
+ {
1396
+ contacts[B2_SIMD_WIDTH * contactBase + k] = NULL;
1397
+ }
1398
+
1399
+ contactBase += colorContactCountSIMD;
1400
+ }
1401
+
1402
+ int colorJointCount = color->jointSims.count;
1403
+ for ( int k = 0; k < colorJointCount; ++k )
1404
+ {
1405
+ joints[jointBase + k] = color->jointSims.data + k;
1406
+ }
1407
+ jointBase += colorJointCount;
1408
+ }
1409
+
1410
+ B2_ASSERT( contactBase == simdContactCount );
1411
+ B2_ASSERT( jointBase == awakeJointCount );
1412
+ }
1413
+
1414
+ // Define work blocks for preparing contacts and storing contact impulses
1415
+ int contactBlockSize = blocksPerWorker;
1416
+ int contactBlockCount = simdContactCount > 0 ? ( ( simdContactCount - 1 ) >> 2 ) + 1 : 0;
1417
+ if ( simdContactCount > contactBlockSize * maxBlockCount )
1418
+ {
1419
+ // Too many blocks, increase block size
1420
+ contactBlockSize = simdContactCount / maxBlockCount;
1421
+ contactBlockCount = maxBlockCount;
1422
+ }
1423
+
1424
+ // Define work blocks for preparing joints
1425
+ int jointBlockSize = blocksPerWorker;
1426
+ int jointBlockCount = awakeJointCount > 0 ? ( ( awakeJointCount - 1 ) >> 2 ) + 1 : 0;
1427
+ if ( awakeJointCount > jointBlockSize * maxBlockCount )
1428
+ {
1429
+ // Too many blocks, increase block size
1430
+ jointBlockSize = awakeJointCount / maxBlockCount;
1431
+ jointBlockCount = maxBlockCount;
1432
+ }
1433
+
1434
+ int stageCount = 0;
1435
+
1436
+ // b2_stagePrepareJoints
1437
+ stageCount += 1;
1438
+ // b2_stagePrepareContacts
1439
+ stageCount += 1;
1440
+ // b2_stageIntegrateVelocities
1441
+ stageCount += 1;
1442
+ // b2_stageWarmStart
1443
+ stageCount += activeColorCount;
1444
+ // b2_stageSolve
1445
+ stageCount += activeColorCount;
1446
+ // b2_stageIntegratePositions
1447
+ stageCount += 1;
1448
+ // b2_stageRelax
1449
+ stageCount += activeColorCount;
1450
+ // b2_stageRestitution
1451
+ stageCount += activeColorCount;
1452
+ // b2_stageStoreImpulses
1453
+ stageCount += 1;
1454
+
1455
+ b2SolverStage* stages = b2AllocateArenaItem( &world->arena, stageCount * sizeof( b2SolverStage ), "stages" );
1456
+ b2SolverBlock* bodyBlocks =
1457
+ b2AllocateArenaItem( &world->arena, bodyBlockCount * sizeof( b2SolverBlock ), "body blocks" );
1458
+ b2SolverBlock* contactBlocks =
1459
+ b2AllocateArenaItem( &world->arena, contactBlockCount * sizeof( b2SolverBlock ), "contact blocks" );
1460
+ b2SolverBlock* jointBlocks =
1461
+ b2AllocateArenaItem( &world->arena, jointBlockCount * sizeof( b2SolverBlock ), "joint blocks" );
1462
+ b2SolverBlock* graphBlocks =
1463
+ b2AllocateArenaItem( &world->arena, graphBlockCount * sizeof( b2SolverBlock ), "graph blocks" );
1464
+
1465
+ // Split an awake island. This modifies:
1466
+ // - stack allocator
1467
+ // - world island array and solver set
1468
+ // - island indices on bodies, contacts, and joints
1469
+ // I'm squeezing this task in here because it may be expensive and this is a safe place to put it.
1470
+ // Note: cannot split islands in parallel with FinalizeBodies
1471
+ void* splitIslandTask = NULL;
1472
+ if ( world->splitIslandId != B2_NULL_INDEX )
1473
+ {
1474
+ splitIslandTask = world->enqueueTaskFcn( &b2SplitIslandTask, 1, 1, world, world->userTaskContext );
1475
+ world->taskCount += 1;
1476
+ world->activeTaskCount += splitIslandTask == NULL ? 0 : 1;
1477
+ }
1478
+
1479
+ // Prepare body work blocks
1480
+ for ( int i = 0; i < bodyBlockCount; ++i )
1481
+ {
1482
+ b2SolverBlock* block = bodyBlocks + i;
1483
+ block->startIndex = i * bodyBlockSize;
1484
+ block->count = (int16_t)bodyBlockSize;
1485
+ block->blockType = b2_bodyBlock;
1486
+ b2AtomicStoreInt(&block->syncIndex, 0);
1487
+ }
1488
+ bodyBlocks[bodyBlockCount - 1].count = (int16_t)( awakeBodyCount - ( bodyBlockCount - 1 ) * bodyBlockSize );
1489
+
1490
+ // Prepare joint work blocks
1491
+ for ( int i = 0; i < jointBlockCount; ++i )
1492
+ {
1493
+ b2SolverBlock* block = jointBlocks + i;
1494
+ block->startIndex = i * jointBlockSize;
1495
+ block->count = (int16_t)jointBlockSize;
1496
+ block->blockType = b2_jointBlock;
1497
+ b2AtomicStoreInt( &block->syncIndex, 0 );
1498
+ }
1499
+
1500
+ if ( jointBlockCount > 0 )
1501
+ {
1502
+ jointBlocks[jointBlockCount - 1].count = (int16_t)( awakeJointCount - ( jointBlockCount - 1 ) * jointBlockSize );
1503
+ }
1504
+
1505
+ // Prepare contact work blocks
1506
+ for ( int i = 0; i < contactBlockCount; ++i )
1507
+ {
1508
+ b2SolverBlock* block = contactBlocks + i;
1509
+ block->startIndex = i * contactBlockSize;
1510
+ block->count = (int16_t)contactBlockSize;
1511
+ block->blockType = b2_contactBlock;
1512
+ b2AtomicStoreInt( &block->syncIndex, 0 );
1513
+ }
1514
+
1515
+ if ( contactBlockCount > 0 )
1516
+ {
1517
+ contactBlocks[contactBlockCount - 1].count =
1518
+ (int16_t)( simdContactCount - ( contactBlockCount - 1 ) * contactBlockSize );
1519
+ }
1520
+
1521
+ // Prepare graph work blocks
1522
+ b2SolverBlock* graphColorBlocks[B2_GRAPH_COLOR_COUNT];
1523
+ b2SolverBlock* baseGraphBlock = graphBlocks;
1524
+
1525
+ for ( int i = 0; i < activeColorCount; ++i )
1526
+ {
1527
+ graphColorBlocks[i] = baseGraphBlock;
1528
+
1529
+ int colorJointBlockCount = colorJointBlockCounts[i];
1530
+ int colorJointBlockSize = colorJointBlockSizes[i];
1531
+ for ( int j = 0; j < colorJointBlockCount; ++j )
1532
+ {
1533
+ b2SolverBlock* block = baseGraphBlock + j;
1534
+ block->startIndex = j * colorJointBlockSize;
1535
+ block->count = (int16_t)colorJointBlockSize;
1536
+ block->blockType = b2_graphJointBlock;
1537
+ b2AtomicStoreInt( &block->syncIndex, 0 );
1538
+ }
1539
+
1540
+ if ( colorJointBlockCount > 0 )
1541
+ {
1542
+ baseGraphBlock[colorJointBlockCount - 1].count =
1543
+ (int16_t)( colorJointCounts[i] - ( colorJointBlockCount - 1 ) * colorJointBlockSize );
1544
+ baseGraphBlock += colorJointBlockCount;
1545
+ }
1546
+
1547
+ int colorContactBlockCount = colorContactBlockCounts[i];
1548
+ int colorContactBlockSize = colorContactBlockSizes[i];
1549
+ for ( int j = 0; j < colorContactBlockCount; ++j )
1550
+ {
1551
+ b2SolverBlock* block = baseGraphBlock + j;
1552
+ block->startIndex = j * colorContactBlockSize;
1553
+ block->count = (int16_t)colorContactBlockSize;
1554
+ block->blockType = b2_graphContactBlock;
1555
+ b2AtomicStoreInt( &block->syncIndex, 0 );
1556
+ }
1557
+
1558
+ if ( colorContactBlockCount > 0 )
1559
+ {
1560
+ baseGraphBlock[colorContactBlockCount - 1].count =
1561
+ (int16_t)( colorContactCounts[i] - ( colorContactBlockCount - 1 ) * colorContactBlockSize );
1562
+ baseGraphBlock += colorContactBlockCount;
1563
+ }
1564
+ }
1565
+
1566
+ B2_ASSERT( (ptrdiff_t)(baseGraphBlock - graphBlocks) == graphBlockCount );
1567
+
1568
+ b2SolverStage* stage = stages;
1569
+
1570
+ // Prepare joints
1571
+ stage->type = b2_stagePrepareJoints;
1572
+ stage->blocks = jointBlocks;
1573
+ stage->blockCount = jointBlockCount;
1574
+ stage->colorIndex = -1;
1575
+ b2AtomicStoreInt(&stage->completionCount, 0);
1576
+ stage += 1;
1577
+
1578
+ // Prepare contacts
1579
+ stage->type = b2_stagePrepareContacts;
1580
+ stage->blocks = contactBlocks;
1581
+ stage->blockCount = contactBlockCount;
1582
+ stage->colorIndex = -1;
1583
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1584
+ stage += 1;
1585
+
1586
+ // Integrate velocities
1587
+ stage->type = b2_stageIntegrateVelocities;
1588
+ stage->blocks = bodyBlocks;
1589
+ stage->blockCount = bodyBlockCount;
1590
+ stage->colorIndex = -1;
1591
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1592
+ stage += 1;
1593
+
1594
+ // Warm start
1595
+ for ( int i = 0; i < activeColorCount; ++i )
1596
+ {
1597
+ stage->type = b2_stageWarmStart;
1598
+ stage->blocks = graphColorBlocks[i];
1599
+ stage->blockCount = colorJointBlockCounts[i] + colorContactBlockCounts[i];
1600
+ stage->colorIndex = activeColorIndices[i];
1601
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1602
+ stage += 1;
1603
+ }
1604
+
1605
+ // Solve graph
1606
+ for ( int i = 0; i < activeColorCount; ++i )
1607
+ {
1608
+ stage->type = b2_stageSolve;
1609
+ stage->blocks = graphColorBlocks[i];
1610
+ stage->blockCount = colorJointBlockCounts[i] + colorContactBlockCounts[i];
1611
+ stage->colorIndex = activeColorIndices[i];
1612
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1613
+ stage += 1;
1614
+ }
1615
+
1616
+ // Integrate positions
1617
+ stage->type = b2_stageIntegratePositions;
1618
+ stage->blocks = bodyBlocks;
1619
+ stage->blockCount = bodyBlockCount;
1620
+ stage->colorIndex = -1;
1621
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1622
+ stage += 1;
1623
+
1624
+ // Relax constraints
1625
+ for ( int i = 0; i < activeColorCount; ++i )
1626
+ {
1627
+ stage->type = b2_stageRelax;
1628
+ stage->blocks = graphColorBlocks[i];
1629
+ stage->blockCount = colorJointBlockCounts[i] + colorContactBlockCounts[i];
1630
+ stage->colorIndex = activeColorIndices[i];
1631
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1632
+ stage += 1;
1633
+ }
1634
+
1635
+ // Restitution
1636
+ // Note: joint blocks mixed in, could have joint limit restitution
1637
+ for ( int i = 0; i < activeColorCount; ++i )
1638
+ {
1639
+ stage->type = b2_stageRestitution;
1640
+ stage->blocks = graphColorBlocks[i];
1641
+ stage->blockCount = colorJointBlockCounts[i] + colorContactBlockCounts[i];
1642
+ stage->colorIndex = activeColorIndices[i];
1643
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1644
+ stage += 1;
1645
+ }
1646
+
1647
+ // Store impulses
1648
+ stage->type = b2_stageStoreImpulses;
1649
+ stage->blocks = contactBlocks;
1650
+ stage->blockCount = contactBlockCount;
1651
+ stage->colorIndex = -1;
1652
+ b2AtomicStoreInt( &stage->completionCount, 0 );
1653
+ stage += 1;
1654
+
1655
+ B2_ASSERT( (int)( stage - stages ) == stageCount );
1656
+
1657
+ B2_ASSERT( workerCount <= B2_MAX_WORKERS );
1658
+ b2WorkerContext workerContext[B2_MAX_WORKERS];
1659
+
1660
+ stepContext->graph = graph;
1661
+ stepContext->joints = joints;
1662
+ stepContext->contacts = contacts;
1663
+ stepContext->simdContactConstraints = simdContactConstraints;
1664
+ stepContext->activeColorCount = activeColorCount;
1665
+ stepContext->workerCount = workerCount;
1666
+ stepContext->stageCount = stageCount;
1667
+ stepContext->stages = stages;
1668
+ b2AtomicStoreU32(&stepContext->atomicSyncBits, 0);
1669
+
1670
+ world->profile.prepareStages = b2GetMillisecondsAndReset( &prepareTicks );
1671
+ b2TracyCZoneEnd( prepare_stages );
1672
+
1673
+ b2TracyCZoneNC( solve_constraints, "Solve Constraints", b2_colorIndigo, true );
1674
+ uint64_t constraintTicks = b2GetTicks();
1675
+
1676
+ // Must use worker index because thread 0 can be assigned multiple tasks by enkiTS
1677
+ for ( int i = 0; i < workerCount; ++i )
1678
+ {
1679
+ workerContext[i].context = stepContext;
1680
+ workerContext[i].workerIndex = i;
1681
+ workerContext[i].userTask = world->enqueueTaskFcn( b2SolverTask, 1, 1, workerContext + i, world->userTaskContext );
1682
+ world->taskCount += 1;
1683
+ world->activeTaskCount += workerContext[i].userTask == NULL ? 0 : 1;
1684
+ }
1685
+
1686
+ // Finish island split
1687
+ if ( splitIslandTask != NULL )
1688
+ {
1689
+ world->finishTaskFcn( splitIslandTask, world->userTaskContext );
1690
+ world->activeTaskCount -= 1;
1691
+ }
1692
+ world->splitIslandId = B2_NULL_INDEX;
1693
+
1694
+ // Finish constraint solve
1695
+ for ( int i = 0; i < workerCount; ++i )
1696
+ {
1697
+ if ( workerContext[i].userTask != NULL )
1698
+ {
1699
+ world->finishTaskFcn( workerContext[i].userTask, world->userTaskContext );
1700
+ world->activeTaskCount -= 1;
1701
+ }
1702
+ }
1703
+
1704
+ world->profile.solveConstraints = b2GetMillisecondsAndReset( &constraintTicks );
1705
+ b2TracyCZoneEnd( solve_constraints );
1706
+
1707
+ b2TracyCZoneNC( update_transforms, "Update Transforms", b2_colorMediumSeaGreen, true );
1708
+ uint64_t transformTicks = b2GetTicks();
1709
+
1710
+ // Prepare contact, enlarged body, and island bit sets used in body finalization.
1711
+ int awakeIslandCount = awakeSet->islandSims.count;
1712
+ for ( int i = 0; i < world->workerCount; ++i )
1713
+ {
1714
+ b2TaskContext* taskContext = world->taskContexts.data + i;
1715
+ b2SetBitCountAndClear( &taskContext->enlargedSimBitSet, awakeBodyCount );
1716
+ b2SetBitCountAndClear( &taskContext->awakeIslandBitSet, awakeIslandCount );
1717
+ taskContext->splitIslandId = B2_NULL_INDEX;
1718
+ taskContext->splitSleepTime = 0.0f;
1719
+ }
1720
+
1721
+ // Finalize bodies. Must happen after the constraint solver and after island splitting.
1722
+ void* finalizeBodiesTask =
1723
+ world->enqueueTaskFcn( b2FinalizeBodiesTask, awakeBodyCount, 64, stepContext, world->userTaskContext );
1724
+ world->taskCount += 1;
1725
+ if ( finalizeBodiesTask != NULL )
1726
+ {
1727
+ world->finishTaskFcn( finalizeBodiesTask, world->userTaskContext );
1728
+ }
1729
+
1730
+ b2FreeArenaItem( &world->arena, graphBlocks );
1731
+ b2FreeArenaItem( &world->arena, jointBlocks );
1732
+ b2FreeArenaItem( &world->arena, contactBlocks );
1733
+ b2FreeArenaItem( &world->arena, bodyBlocks );
1734
+ b2FreeArenaItem( &world->arena, stages );
1735
+ b2FreeArenaItem( &world->arena, overflowContactConstraints );
1736
+ b2FreeArenaItem( &world->arena, simdContactConstraints );
1737
+ b2FreeArenaItem( &world->arena, joints );
1738
+ b2FreeArenaItem( &world->arena, contacts );
1739
+
1740
+ world->profile.transforms = b2GetMilliseconds( transformTicks );
1741
+ b2TracyCZoneEnd( update_transforms );
1742
+ }
1743
+
1744
+ // Report hit events
1745
+ // todo_erin perhaps optimize this with a bitset
1746
+ // todo_erin perhaps do this in parallel with other work below
1747
+ {
1748
+ b2TracyCZoneNC( hit_events, "Hit Events", b2_colorRosyBrown, true );
1749
+ uint64_t hitTicks = b2GetTicks();
1750
+
1751
+ B2_ASSERT( world->contactHitEvents.count == 0 );
1752
+
1753
+ float threshold = world->hitEventThreshold;
1754
+ b2GraphColor* colors = world->constraintGraph.colors;
1755
+ for ( int i = 0; i < B2_GRAPH_COLOR_COUNT; ++i )
1756
+ {
1757
+ b2GraphColor* color = colors + i;
1758
+ int contactCount = color->contactSims.count;
1759
+ b2ContactSim* contactSims = color->contactSims.data;
1760
+ for ( int j = 0; j < contactCount; ++j )
1761
+ {
1762
+ b2ContactSim* contactSim = contactSims + j;
1763
+ if ( ( contactSim->simFlags & b2_simEnableHitEvent ) == 0 )
1764
+ {
1765
+ continue;
1766
+ }
1767
+
1768
+ b2ContactHitEvent event = { 0 };
1769
+ event.approachSpeed = threshold;
1770
+
1771
+ bool hit = false;
1772
+ int pointCount = contactSim->manifold.pointCount;
1773
+ for ( int k = 0; k < pointCount; ++k )
1774
+ {
1775
+ b2ManifoldPoint* mp = contactSim->manifold.points + k;
1776
+ float approachSpeed = -mp->normalVelocity;
1777
+
1778
+ // Need to check total impulse because the point may be speculative and not colliding
1779
+ if ( approachSpeed > event.approachSpeed && mp->totalNormalImpulse > 0.0f )
1780
+ {
1781
+ event.approachSpeed = approachSpeed;
1782
+ event.point = mp->point;
1783
+ hit = true;
1784
+ }
1785
+ }
1786
+
1787
+ if ( hit == true )
1788
+ {
1789
+ event.normal = contactSim->manifold.normal;
1790
+
1791
+ b2Shape* shapeA = b2ShapeArray_Get( &world->shapes, contactSim->shapeIdA );
1792
+ b2Shape* shapeB = b2ShapeArray_Get( &world->shapes, contactSim->shapeIdB );
1793
+
1794
+ event.shapeIdA = ( b2ShapeId ){ shapeA->id + 1, world->worldId, shapeA->generation };
1795
+ event.shapeIdB = ( b2ShapeId ){ shapeB->id + 1, world->worldId, shapeB->generation };
1796
+
1797
+ b2ContactHitEventArray_Push( &world->contactHitEvents, event );
1798
+ }
1799
+ }
1800
+ }
1801
+
1802
+ world->profile.hitEvents = b2GetMilliseconds( hitTicks );
1803
+ b2TracyCZoneEnd( hit_events );
1804
+ }
1805
+
1806
+ {
1807
+ b2TracyCZoneNC( refit_bvh, "Refit BVH", b2_colorFireBrick, true );
1808
+ uint64_t refitTicks = b2GetTicks();
1809
+
1810
+ // Finish the user tree task that was queued earlier in the time step. This must be complete before touching the
1811
+ // broad-phase.
1812
+ if ( world->userTreeTask != NULL )
1813
+ {
1814
+ world->finishTaskFcn( world->userTreeTask, world->userTaskContext );
1815
+ world->userTreeTask = NULL;
1816
+ world->activeTaskCount -= 1;
1817
+ }
1818
+
1819
+ b2ValidateNoEnlarged( &world->broadPhase );
1820
+
1821
+ // Gather bits for all sim bodies that have enlarged AABBs
1822
+ b2BitSet* enlargedBodyBitSet = &world->taskContexts.data[0].enlargedSimBitSet;
1823
+ for ( int i = 1; i < world->workerCount; ++i )
1824
+ {
1825
+ b2InPlaceUnion( enlargedBodyBitSet, &world->taskContexts.data[i].enlargedSimBitSet );
1826
+ }
1827
+
1828
+ // Enlarge broad-phase proxies and build move array
1829
+ // Apply shape AABB changes to broad-phase. This also create the move array which must be
1830
+ // in deterministic order. I'm tracking sim bodies because the number of shape ids can be huge.
1831
+ // This has to happen before bullets are processed.
1832
+ {
1833
+ b2BroadPhase* broadPhase = &world->broadPhase;
1834
+ uint32_t wordCount = enlargedBodyBitSet->blockCount;
1835
+ uint64_t* bits = enlargedBodyBitSet->bits;
1836
+
1837
+ // Fast array access is important here
1838
+ b2Body* bodyArray = world->bodies.data;
1839
+ b2BodySim* bodySimArray = awakeSet->bodySims.data;
1840
+ b2Shape* shapeArray = world->shapes.data;
1841
+
1842
+ for ( uint32_t k = 0; k < wordCount; ++k )
1843
+ {
1844
+ uint64_t word = bits[k];
1845
+ while ( word != 0 )
1846
+ {
1847
+ uint32_t ctz = b2CTZ64( word );
1848
+ uint32_t bodySimIndex = 64 * k + ctz;
1849
+
1850
+ b2BodySim* bodySim = bodySimArray + bodySimIndex;
1851
+
1852
+ b2Body* body = bodyArray + bodySim->bodyId;
1853
+
1854
+ int shapeId = body->headShapeId;
1855
+ if ( bodySim->isBullet && bodySim->isFast )
1856
+ {
1857
+ // Fast bullet bodies don't have their final AABB yet
1858
+ while ( shapeId != B2_NULL_INDEX )
1859
+ {
1860
+ b2Shape* shape = shapeArray + shapeId;
1861
+
1862
+ // Shape is fast. It's aabb will be enlarged in continuous collision.
1863
+ // Update the move array here for determinism because bullets are processed
1864
+ // below in non-deterministic order.
1865
+ b2BufferMove( broadPhase, shape->proxyKey );
1866
+
1867
+ shapeId = shape->nextShapeId;
1868
+ }
1869
+ }
1870
+ else
1871
+ {
1872
+ while ( shapeId != B2_NULL_INDEX )
1873
+ {
1874
+ b2Shape* shape = shapeArray + shapeId;
1875
+
1876
+ // The AABB may not have been enlarged, despite the body being flagged as enlarged.
1877
+ // For example, a body with multiple shapes may have not have all shapes enlarged.
1878
+ // A fast body may have been flagged as enlarged despite having no shapes enlarged.
1879
+ if ( shape->enlargedAABB )
1880
+ {
1881
+ b2BroadPhase_EnlargeProxy( broadPhase, shape->proxyKey, shape->fatAABB );
1882
+ shape->enlargedAABB = false;
1883
+ }
1884
+
1885
+ shapeId = shape->nextShapeId;
1886
+ }
1887
+ }
1888
+
1889
+ // Clear the smallest set bit
1890
+ word = word & ( word - 1 );
1891
+ }
1892
+ }
1893
+ }
1894
+
1895
+ b2ValidateBroadphase( &world->broadPhase );
1896
+
1897
+ world->profile.refit = b2GetMilliseconds( refitTicks );
1898
+ b2TracyCZoneEnd( refit_bvh );
1899
+ }
1900
+
1901
+ int bulletBodyCount = b2AtomicLoadInt( &stepContext->bulletBodyCount );
1902
+ if ( bulletBodyCount > 0 )
1903
+ {
1904
+ b2TracyCZoneNC( bullets, "Bullets", b2_colorLightYellow, true );
1905
+ uint64_t bulletTicks = b2GetTicks();
1906
+
1907
+ // Fast bullet bodies
1908
+ // Note: a bullet body may be moving slow
1909
+ int minRange = 8;
1910
+ void* userBulletBodyTask = world->enqueueTaskFcn( &b2BulletBodyTask, bulletBodyCount, minRange, stepContext,
1911
+ world->userTaskContext );
1912
+ world->taskCount += 1;
1913
+ if ( userBulletBodyTask != NULL )
1914
+ {
1915
+ world->finishTaskFcn( userBulletBodyTask, world->userTaskContext );
1916
+ }
1917
+
1918
+ // Serially enlarge broad-phase proxies for bullet shapes
1919
+ b2BroadPhase* broadPhase = &world->broadPhase;
1920
+ b2DynamicTree* dynamicTree = broadPhase->trees + b2_dynamicBody;
1921
+
1922
+ // Fast array access is important here
1923
+ b2Body* bodyArray = world->bodies.data;
1924
+ b2BodySim* bodySimArray = awakeSet->bodySims.data;
1925
+ b2Shape* shapeArray = world->shapes.data;
1926
+
1927
+ // Serially enlarge broad-phase proxies for bullet shapes
1928
+ int* bulletBodySimIndices = stepContext->bulletBodies;
1929
+
1930
+ // This loop has non-deterministic order but it shouldn't affect the result
1931
+ for ( int i = 0; i < bulletBodyCount; ++i )
1932
+ {
1933
+ b2BodySim* bulletBodySim = bodySimArray + bulletBodySimIndices[i];
1934
+ if ( bulletBodySim->enlargeAABB == false )
1935
+ {
1936
+ continue;
1937
+ }
1938
+
1939
+ // clear flag
1940
+ bulletBodySim->enlargeAABB = false;
1941
+
1942
+ int bodyId = bulletBodySim->bodyId;
1943
+ B2_ASSERT( 0 <= bodyId && bodyId < world->bodies.count );
1944
+ b2Body* bulletBody = bodyArray + bodyId;
1945
+
1946
+ int shapeId = bulletBody->headShapeId;
1947
+ while ( shapeId != B2_NULL_INDEX )
1948
+ {
1949
+ b2Shape* shape = shapeArray + shapeId;
1950
+ if ( shape->enlargedAABB == false )
1951
+ {
1952
+ shapeId = shape->nextShapeId;
1953
+ continue;
1954
+ }
1955
+
1956
+ // clear flag
1957
+ shape->enlargedAABB = false;
1958
+
1959
+ int proxyKey = shape->proxyKey;
1960
+ int proxyId = B2_PROXY_ID( proxyKey );
1961
+ B2_ASSERT( B2_PROXY_TYPE( proxyKey ) == b2_dynamicBody );
1962
+
1963
+ // all fast bullet shapes should already be in the move buffer
1964
+ B2_ASSERT( b2ContainsKey( &broadPhase->moveSet, proxyKey + 1 ) );
1965
+
1966
+ b2DynamicTree_EnlargeProxy( dynamicTree, proxyId, shape->fatAABB );
1967
+
1968
+ shapeId = shape->nextShapeId;
1969
+ }
1970
+ }
1971
+
1972
+ world->profile.bullets = b2GetMilliseconds( bulletTicks );
1973
+ b2TracyCZoneEnd( bullets );
1974
+ }
1975
+
1976
+ // Need to free this even if no bullets got processed.
1977
+ b2FreeArenaItem( &world->arena, stepContext->bulletBodies );
1978
+ stepContext->bulletBodies = NULL;
1979
+ b2AtomicStoreInt(&stepContext->bulletBodyCount, 0);
1980
+
1981
+ // Island sleeping
1982
+ // This must be done last because putting islands to sleep invalidates the enlarged body bits.
1983
+ // todo_erin figure out how to do this in parallel with tree refit
1984
+ if ( world->enableSleep == true )
1985
+ {
1986
+ b2TracyCZoneNC( sleep_islands, "Island Sleep", b2_colorLightSlateGray, true );
1987
+ uint64_t sleepTicks = b2GetTicks();
1988
+
1989
+ // Collect split island candidate for the next time step. No need to split if sleeping is disabled.
1990
+ B2_ASSERT( world->splitIslandId == B2_NULL_INDEX );
1991
+ float splitSleepTimer = 0.0f;
1992
+ for ( int i = 0; i < world->workerCount; ++i )
1993
+ {
1994
+ b2TaskContext* taskContext = world->taskContexts.data + i;
1995
+ if ( taskContext->splitIslandId != B2_NULL_INDEX && taskContext->splitSleepTime >= splitSleepTimer )
1996
+ {
1997
+ B2_ASSERT( taskContext->splitSleepTime > 0.0f );
1998
+
1999
+ // Tie breaking for determinism. Largest island id wins. Needed due to work stealing.
2000
+ if ( taskContext->splitSleepTime == splitSleepTimer && taskContext->splitIslandId < world->splitIslandId )
2001
+ {
2002
+ continue;
2003
+ }
2004
+
2005
+ world->splitIslandId = taskContext->splitIslandId;
2006
+ splitSleepTimer = taskContext->splitSleepTime;
2007
+ }
2008
+ }
2009
+
2010
+ b2BitSet* awakeIslandBitSet = &world->taskContexts.data[0].awakeIslandBitSet;
2011
+ for ( int i = 1; i < world->workerCount; ++i )
2012
+ {
2013
+ b2InPlaceUnion( awakeIslandBitSet, &world->taskContexts.data[i].awakeIslandBitSet );
2014
+ }
2015
+
2016
+ // Need to process in reverse because this moves islands to sleeping solver sets.
2017
+ b2IslandSim* islands = awakeSet->islandSims.data;
2018
+ int count = awakeSet->islandSims.count;
2019
+ for ( int islandIndex = count - 1; islandIndex >= 0; islandIndex -= 1 )
2020
+ {
2021
+ if ( b2GetBit( awakeIslandBitSet, islandIndex ) == true )
2022
+ {
2023
+ // this island is still awake
2024
+ continue;
2025
+ }
2026
+
2027
+ b2IslandSim* island = islands + islandIndex;
2028
+ int islandId = island->islandId;
2029
+
2030
+ b2TrySleepIsland( world, islandId );
2031
+ }
2032
+
2033
+ b2ValidateSolverSets( world );
2034
+
2035
+ world->profile.sleepIslands = b2GetMilliseconds( sleepTicks );
2036
+ b2TracyCZoneEnd( sleep_islands );
2037
+ }
2038
+ }