babylon-box3d 0.6.1 → 0.8.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 (41) hide show
  1. package/README.md +80 -5
  2. package/dist/box3dPlugin.d.ts +22 -0
  3. package/dist/box3dPlugin.d.ts.map +1 -1
  4. package/dist/box3dPlugin.js +33 -2
  5. package/dist/box3dPlugin.js.map +1 -1
  6. package/dist/loadBox3D.d.ts +7 -0
  7. package/dist/loadBox3D.d.ts.map +1 -1
  8. package/dist/loadBox3D.js +5 -4
  9. package/dist/loadBox3D.js.map +1 -1
  10. package/lib/esm/box3d.d.ts +54 -1
  11. package/lib/esm/box3d.js +1 -1
  12. package/lib/esm/box3d.wasm +0 -0
  13. package/lib/esm-f64/box3d.d.ts +320 -0
  14. package/lib/esm-f64/box3d.js +2 -0
  15. package/lib/esm-f64/box3d.wasm +0 -0
  16. package/lib/esm-f64-threads/box3d.d.ts +320 -0
  17. package/lib/esm-f64-threads/box3d.js +2 -0
  18. package/lib/esm-f64-threads/box3d.wasm +0 -0
  19. package/lib/esm-threads/box3d.d.ts +54 -1
  20. package/lib/esm-threads/box3d.js +1 -1
  21. package/lib/esm-threads/box3d.wasm +0 -0
  22. package/lib/node/box3d.d.ts +54 -1
  23. package/lib/node/box3d.mjs +1 -1
  24. package/lib/node/box3d.wasm +0 -0
  25. package/lib/node-f64/box3d.d.ts +320 -0
  26. package/lib/node-f64/box3d.mjs +2 -0
  27. package/lib/node-f64/box3d.wasm +0 -0
  28. package/lib/node-f64-threads/box3d.d.ts +320 -0
  29. package/lib/node-f64-threads/box3d.mjs +2 -0
  30. package/lib/node-f64-threads/box3d.wasm +0 -0
  31. package/lib/node-threads/box3d.d.ts +54 -1
  32. package/lib/node-threads/box3d.mjs +1 -1
  33. package/lib/node-threads/box3d.wasm +0 -0
  34. package/lib/umd/box3d.umd.js +1 -1
  35. package/lib/umd/box3d.umd.wasm +0 -0
  36. package/package.json +17 -1
  37. package/umd/babylon.box3d.js +76 -4
  38. package/umd/babylon.box3d.js.map +3 -3
  39. package/umd/babylon.box3d.min.js +2 -2
  40. package/umd/babylon.box3d.min.js.map +4 -4
  41. package/wasm/box3d_shim.c +526 -35
package/wasm/box3d_shim.c CHANGED
@@ -50,6 +50,54 @@ BX_EXPORT int bx_GetVersion( void )
50
50
  return v.major * 10000 + v.minor * 100 + v.revision;
51
51
  }
52
52
 
53
+ // World positions cross the boundary in doubles. Entry points take them as doubles, and those that give one back
54
+ // write it to s_scratchF64 (or a double buffer beside their float records) as well as to the float scratch. A build with
55
+ // BOX3D_DOUBLE_PRECISION keeps them exact far from the origin; the default build holds floats, so there the doubles
56
+ // are its floats widened, and the floats are what they always were.
57
+ #define BX_SCRATCH_F64_SIZE 16
58
+ static double s_scratchF64[BX_SCRATCH_F64_SIZE];
59
+
60
+ BX_EXPORT double* bx_ScratchF64( void )
61
+ {
62
+ return s_scratchF64;
63
+ }
64
+
65
+ /// 1 when this module was built with BOX3D_DOUBLE_PRECISION, so world positions are held in doubles.
66
+ BX_EXPORT int bx_IsDoublePrecision( void )
67
+ {
68
+ return b3IsDoublePrecision() ? 1 : 0;
69
+ }
70
+
71
+ typedef struct bxDoubleBuffer
72
+ {
73
+ double* data;
74
+ int count;
75
+ int capacity;
76
+ } bxDoubleBuffer;
77
+
78
+ static void bxDoubleBuffer_Reserve( bxDoubleBuffer* buf, int count )
79
+ {
80
+ if ( count <= buf->capacity )
81
+ {
82
+ return;
83
+ }
84
+ int capacity = buf->capacity == 0 ? 256 : buf->capacity;
85
+ while ( capacity < count )
86
+ {
87
+ capacity *= 2;
88
+ }
89
+ buf->data = (double*)realloc( buf->data, (size_t)capacity * sizeof( double ) );
90
+ buf->capacity = capacity;
91
+ }
92
+
93
+ /// A world position from three doubles: exact in a double precision build, rounded to float in the default one, as a
94
+ /// float argument always was.
95
+ static b3Pos bxPos( double x, double y, double z )
96
+ {
97
+ b3Pos p = { x, y, z };
98
+ return p;
99
+ }
100
+
53
101
  typedef struct bxFloatBuffer
54
102
  {
55
103
  float* data;
@@ -106,8 +154,19 @@ typedef struct bxBody
106
154
  bxHelper* helpers;
107
155
  int helperCount;
108
156
  int helperCapacity;
157
+ // The body's feature key: each of its shapes gets (featureKey << BX_FEATURE_INDEX_BITS) | its index in the body as
158
+ // Box3D's b3ShapeDef::featureKey, which orders new contacts, continuous collision and cast ties. 0 until it is set.
159
+ uint64_t featureKey;
160
+ // A clip plane (wasm/patches/0006): contact points on its positive side, where n.p + d > 0 like Babylon's clip
161
+ // planes, or within twice the linear slop of it, are dropped from this body's contacts with shapes whose category
162
+ // bits meet clipMask. Off while clipEnabled is 0.
163
+ int clipEnabled;
164
+ double clipPlane[4];
165
+ uint64_t clipMask;
109
166
  } bxBody;
110
167
 
168
+ #define BX_FEATURE_INDEX_BITS 20
169
+
111
170
  static void bxReleaseBodyShapes( bxBody* body, int destroyShapes );
112
171
 
113
172
  static bxBody* s_bodies;
@@ -154,6 +213,10 @@ typedef struct bxShapeDesc
154
213
  int groupIndex;
155
214
  float rollingResistance;
156
215
  int isSensor;
216
+ // Box3D's b3ShapeDef::invokeContactCreation, on by default: a static shape made with it starts in the broad phase's
217
+ // move buffer and looks for its own pairs on the next step. Off, it waits to be found by a moving shape. A container
218
+ // that has it off turns it off for every shape made from its children.
219
+ int invokeContactCreation;
157
220
  bxChild* children;
158
221
  int childCount;
159
222
  int childCapacity;
@@ -546,6 +609,129 @@ static void bxResetTasks( void )
546
609
  // World
547
610
  // ---------------------------------------------------------------------------------------------
548
611
 
612
+ // ---------------------------------------------------------------------------------------------
613
+ // Clip planes (wasm/patches/0006)
614
+ // ---------------------------------------------------------------------------------------------
615
+
616
+ // Called from Box3D's workers during a step: only reads.
617
+ static const bxBody* bxClipBody( b3ShapeId shapeId, b3ShapeId otherId )
618
+ {
619
+ int slot = (int)(intptr_t)b3Body_GetUserData( b3Shape_GetBody( shapeId ) );
620
+ const bxBody* body = s_bodies + slot;
621
+ if ( slot <= 0 || slot >= s_bodyCount || body->clipEnabled == 0 )
622
+ {
623
+ return NULL;
624
+ }
625
+ return ( b3Shape_GetFilter( otherId ).categoryBits & body->clipMask ) != 0 ? body : NULL;
626
+ }
627
+
628
+ static int bxIsClipped( const bxBody* body, b3Pos point )
629
+ {
630
+ if ( body == NULL )
631
+ {
632
+ return 0;
633
+ }
634
+ // A surface lying in the plane, such as the wall a portal is on, is clipped too: points within twice the linear
635
+ // slop in front of the plane count as past it, which covers a resting contact's point between the surfaces and the
636
+ // 5 mm skin mesh and height field contacts carry.
637
+ const double* n = body->clipPlane;
638
+ return n[0] * point.x + n[1] * point.y + n[2] * point.z + n[3] > -2.0 * B3_LINEAR_SLOP;
639
+ }
640
+
641
+ // Drops the points either body's clip plane clips; Box3D drops the manifolds left empty.
642
+ static int bxClipManifolds( b3ShapeId shapeIdA, b3ShapeId shapeIdB, b3Pos originA, b3Manifold* manifolds, int manifoldCount,
643
+ void* context )
644
+ {
645
+ (void)context;
646
+ const bxBody* a = bxClipBody( shapeIdA, shapeIdB );
647
+ const bxBody* b = bxClipBody( shapeIdB, shapeIdA );
648
+ if ( a == NULL && b == NULL )
649
+ {
650
+ return manifoldCount;
651
+ }
652
+ for ( int i = 0; i < manifoldCount; ++i )
653
+ {
654
+ b3Manifold* manifold = manifolds + i;
655
+ int kept = 0;
656
+ for ( int j = 0; j < manifold->pointCount; ++j )
657
+ {
658
+ // A speculative point is made while the surfaces are still apart, by up to the distance the body covers in
659
+ // the step against a mesh, and it lies midway between them (convex shapes) or on shape B (meshes and height
660
+ // fields). Either way it can sit in front of the plane while the surface it would push on lies in it, and a
661
+ // body falling into a portal would stop dead on the surface the portal is on. So each body also tests the
662
+ // point moved one separation toward the other shape, along the normal, which points from A to B: on or past
663
+ // that shape's surface.
664
+ const b3ManifoldPoint* mp = manifold->points + j;
665
+ b3Pos point = b3OffsetPos( originA, mp->anchorA );
666
+ b3Vec3 gap = b3MulSV( mp->separation, manifold->normal );
667
+ int clipped = bxIsClipped( a, point ) || bxIsClipped( a, b3OffsetPos( point, gap ) ) || bxIsClipped( b, point ) ||
668
+ bxIsClipped( b, b3OffsetPos( point, b3Neg( gap ) ) );
669
+ if ( clipped == 0 )
670
+ {
671
+ manifold->points[kept] = manifold->points[j];
672
+ kept += 1;
673
+ }
674
+ }
675
+ manifold->pointCount = kept;
676
+ }
677
+ return manifoldCount;
678
+ }
679
+
680
+ // Continuous collision has only its time of impact point.
681
+ static bool bxClipPreSolve( b3ShapeId shapeIdA, b3ShapeId shapeIdB, b3Pos point, b3Vec3 normal, void* context )
682
+ {
683
+ (void)normal;
684
+ (void)context;
685
+ return bxIsClipped( bxClipBody( shapeIdA, shapeIdB ), point ) == 0 && bxIsClipped( bxClipBody( shapeIdB, shapeIdA ), point ) == 0;
686
+ }
687
+
688
+ /// Clips the body's contacts with a plane, see bxBody::clipPlane; enabled 0 removes it. The plane is normalized, so
689
+ /// its normal need not be unit length. Wakes the body so its contacts are made again.
690
+ BX_EXPORT void bx_Body_SetClipPlane( int slot, int enabled, double nx, double ny, double nz, double d, unsigned int mask )
691
+ {
692
+ bxBody* body = bxGetBody( slot );
693
+ if ( body == NULL || b3Body_IsValid( body->id ) == false )
694
+ {
695
+ return;
696
+ }
697
+ double length = sqrt( nx * nx + ny * ny + nz * nz );
698
+ body->clipEnabled = enabled != 0 && length > 0.0;
699
+ length = length > 0.0 ? length : 1.0;
700
+ body->clipPlane[0] = nx / length;
701
+ body->clipPlane[1] = ny / length;
702
+ body->clipPlane[2] = nz / length;
703
+ body->clipPlane[3] = d / length;
704
+ body->clipMask = (uint64_t)mask;
705
+ for ( int i = 0; i < body->shapeCount; ++i )
706
+ {
707
+ if ( b3Shape_IsValid( body->shapes[i] ) )
708
+ {
709
+ b3Shape_EnablePreSolveEvents( body->shapes[i], body->clipEnabled != 0 );
710
+ }
711
+ }
712
+ if ( b3Body_GetType( body->id ) != b3_staticBody )
713
+ {
714
+ b3Body_SetAwake( body->id, true );
715
+ }
716
+ }
717
+
718
+ /// scratchF64: [enabled, nx, ny, nz, d, mask]
719
+ BX_EXPORT void bx_Body_GetClipPlane( int slot )
720
+ {
721
+ memset( s_scratchF64, 0, 6 * sizeof( double ) );
722
+ bxBody* body = bxGetBody( slot );
723
+ if ( body == NULL || b3Body_IsValid( body->id ) == false )
724
+ {
725
+ return;
726
+ }
727
+ s_scratchF64[0] = body->clipEnabled;
728
+ for ( int i = 0; i < 4; ++i )
729
+ {
730
+ s_scratchF64[1 + i] = body->clipPlane[i];
731
+ }
732
+ s_scratchF64[5] = (double)( body->clipMask & 0xffffffffu );
733
+ }
734
+
549
735
  BX_EXPORT int bx_GetMaxWorkers( void )
550
736
  {
551
737
  return BX_MAX_WORKERS;
@@ -582,6 +768,9 @@ BX_EXPORT int bx_CreateWorld( float gx, float gy, float gz, int workerCount )
582
768
  }
583
769
  #endif
584
770
  s_worlds[i] = b3CreateWorld( &def );
771
+ // both only run for shapes with pre-solve events, which only clip planes turn on
772
+ b3World_SetManifoldCallback( s_worlds[i], bxClipManifolds, NULL );
773
+ b3World_SetPreSolveCallback( s_worlds[i], bxClipPreSolve, NULL );
585
774
  return i + 1;
586
775
  }
587
776
  }
@@ -685,7 +874,7 @@ BX_EXPORT void bx_World_SetContactTuning( int w, float hertz, float dampingRatio
685
874
  }
686
875
  }
687
876
 
688
- BX_EXPORT void bx_World_Explode( int w, float px, float py, float pz, float radius, float falloff, float impulsePerArea )
877
+ BX_EXPORT void bx_World_Explode( int w, double px, double py, double pz, float radius, float falloff, float impulsePerArea )
689
878
  {
690
879
  b3WorldId worldId = bxGetWorld( w );
691
880
  if ( B3_IS_NULL( worldId ) )
@@ -693,7 +882,7 @@ BX_EXPORT void bx_World_Explode( int w, float px, float py, float pz, float radi
693
882
  return;
694
883
  }
695
884
  b3ExplosionDef def = b3DefaultExplosionDef();
696
- def.position = bxVec3( px, py, pz );
885
+ def.position = bxPos( px, py, pz );
697
886
  def.radius = radius;
698
887
  def.falloff = falloff;
699
888
  def.impulsePerArea = impulsePerArea;
@@ -714,6 +903,13 @@ BX_EXPORT int bx_World_GetJointEventCount( int w )
714
903
  return B3_IS_NON_NULL( worldId ) ? b3World_GetJointEvents( worldId ).count : 0;
715
904
  }
716
905
 
906
+ /// The most the world's step stack has held at once, in bytes. Box3D grows the stack to 1.5 times this and keeps it.
907
+ BX_EXPORT int bx_World_GetStackUsed( int w )
908
+ {
909
+ b3WorldId worldId = bxGetWorld( w );
910
+ return B3_IS_NON_NULL( worldId ) ? b3World_GetCounters( worldId ).stackUsed : 0;
911
+ }
912
+
717
913
  /// scratch: [bodyCount, shapeCount, contactCount, jointCount, islandCount, stepMs, collideMs, solveMs]
718
914
  BX_EXPORT void bx_World_GetStats( int w )
719
915
  {
@@ -985,7 +1181,7 @@ BX_EXPORT int bx_World_GetSensorEvents( int w )
985
1181
  // ---------------------------------------------------------------------------------------------
986
1182
 
987
1183
  /// type: 0 static, 1 kinematic, 2 dynamic
988
- BX_EXPORT int bx_CreateBody( int w, int type, float px, float py, float pz, float qx, float qy, float qz, float qw, int isAwake )
1184
+ BX_EXPORT int bx_CreateBody( int w, int type, double px, double py, double pz, float qx, float qy, float qz, float qw, int isAwake )
989
1185
  {
990
1186
  b3WorldId worldId = bxGetWorld( w );
991
1187
  if ( B3_IS_NULL( worldId ) )
@@ -996,7 +1192,7 @@ BX_EXPORT int bx_CreateBody( int w, int type, float px, float py, float pz, floa
996
1192
  bxBody* body = s_bodies + slot;
997
1193
  b3BodyDef def = b3DefaultBodyDef();
998
1194
  def.type = (b3BodyType)type;
999
- def.position = bxVec3( px, py, pz );
1195
+ def.position = bxPos( px, py, pz );
1000
1196
  def.rotation = bxQuat( qx, qy, qz, qw );
1001
1197
  def.isAwake = isAwake != 0;
1002
1198
  def.userData = (void*)(intptr_t)slot;
@@ -1092,37 +1288,47 @@ BX_EXPORT int bx_Body_GetType( int slot )
1092
1288
  return (int)b3Body_GetType( body->id );
1093
1289
  }
1094
1290
 
1095
- BX_EXPORT void bx_Body_SetTransform( int slot, float px, float py, float pz, float qx, float qy, float qz, float qw )
1291
+ BX_EXPORT void bx_Body_SetTransform( int slot, double px, double py, double pz, float qx, float qy, float qz, float qw )
1096
1292
  {
1097
1293
  BX_BODY( slot );
1098
- b3Body_SetTransform( body->id, bxVec3( px, py, pz ), bxQuat( qx, qy, qz, qw ) );
1294
+ b3Body_SetTransform( body->id, bxPos( px, py, pz ), bxQuat( qx, qy, qz, qw ) );
1099
1295
  if ( body->helperCount > 0 )
1100
1296
  {
1101
- b3Transform xf = bxTransform( px, py, pz, qx, qy, qz, qw );
1297
+ b3WorldTransform xf;
1298
+ xf.p = bxPos( px, py, pz );
1299
+ xf.q = bxTransform( 0.0f, 0.0f, 0.0f, qx, qy, qz, qw ).q;
1102
1300
  for ( int i = 0; i < body->helperCount; ++i )
1103
1301
  {
1104
1302
  if ( b3Body_IsValid( body->helpers[i].id ) )
1105
1303
  {
1106
- b3Transform h = b3MulTransforms( xf, body->helpers[i].local );
1304
+ b3WorldTransform h = b3MulWorldTransforms( xf, body->helpers[i].local );
1107
1305
  b3Body_SetTransform( body->helpers[i].id, h.p, h.q );
1108
1306
  }
1109
1307
  }
1110
1308
  }
1111
1309
  }
1112
1310
 
1113
- /// scratch: [px, py, pz, qx, qy, qz, qw]
1311
+ /// scratch: [px, py, pz, qx, qy, qz, qw]; scratchF64: [px, py, pz], the position as the build holds it
1114
1312
  BX_EXPORT void bx_Body_GetTransform( int slot )
1115
1313
  {
1116
1314
  BX_BODY( slot );
1117
1315
  b3WorldTransform xf = b3Body_GetTransform( body->id );
1118
- bxWriteVec3( 0, xf.p );
1316
+ s_scratch[0] = (float)xf.p.x;
1317
+ s_scratch[1] = (float)xf.p.y;
1318
+ s_scratch[2] = (float)xf.p.z;
1119
1319
  bxWriteQuat( 3, xf.q );
1320
+ s_scratchF64[0] = xf.p.x;
1321
+ s_scratchF64[1] = xf.p.y;
1322
+ s_scratchF64[2] = xf.p.z;
1120
1323
  }
1121
1324
 
1122
- BX_EXPORT void bx_Body_SetTargetTransform( int slot, float px, float py, float pz, float qx, float qy, float qz, float qw, float dt )
1325
+ BX_EXPORT void bx_Body_SetTargetTransform( int slot, double px, double py, double pz, float qx, float qy, float qz, float qw,
1326
+ float dt )
1123
1327
  {
1124
1328
  BX_BODY( slot );
1125
- b3WorldTransform xf = bxTransform( px, py, pz, qx, qy, qz, qw );
1329
+ b3WorldTransform xf;
1330
+ xf.p = bxPos( px, py, pz );
1331
+ xf.q = bxTransform( 0.0f, 0.0f, 0.0f, qx, qy, qz, qw ).q;
1126
1332
  b3Body_SetTargetTransform( body->id, xf, dt, true );
1127
1333
  }
1128
1334
 
@@ -1186,10 +1392,10 @@ BX_EXPORT float bx_Body_GetGravityScale( int slot )
1186
1392
  return b3Body_GetGravityScale( body->id );
1187
1393
  }
1188
1394
 
1189
- BX_EXPORT void bx_Body_ApplyForce( int slot, float fx, float fy, float fz, float px, float py, float pz )
1395
+ BX_EXPORT void bx_Body_ApplyForce( int slot, float fx, float fy, float fz, double px, double py, double pz )
1190
1396
  {
1191
1397
  BX_BODY( slot );
1192
- b3Body_ApplyForce( body->id, bxVec3( fx, fy, fz ), bxVec3( px, py, pz ), true );
1398
+ b3Body_ApplyForce( body->id, bxVec3( fx, fy, fz ), bxPos( px, py, pz ), true );
1193
1399
  }
1194
1400
 
1195
1401
  BX_EXPORT void bx_Body_ApplyForceToCenter( int slot, float fx, float fy, float fz )
@@ -1204,10 +1410,10 @@ BX_EXPORT void bx_Body_ApplyTorque( int slot, float x, float y, float z )
1204
1410
  b3Body_ApplyTorque( body->id, bxVec3( x, y, z ), true );
1205
1411
  }
1206
1412
 
1207
- BX_EXPORT void bx_Body_ApplyLinearImpulse( int slot, float ix, float iy, float iz, float px, float py, float pz )
1413
+ BX_EXPORT void bx_Body_ApplyLinearImpulse( int slot, float ix, float iy, float iz, double px, double py, double pz )
1208
1414
  {
1209
1415
  BX_BODY( slot );
1210
- b3Body_ApplyLinearImpulse( body->id, bxVec3( ix, iy, iz ), bxVec3( px, py, pz ), true );
1416
+ b3Body_ApplyLinearImpulse( body->id, bxVec3( ix, iy, iz ), bxPos( px, py, pz ), true );
1211
1417
  }
1212
1418
 
1213
1419
  BX_EXPORT void bx_Body_ApplyLinearImpulseToCenter( int slot, float ix, float iy, float iz )
@@ -1301,6 +1507,15 @@ BX_EXPORT float* bx_BodyContactsPtr( void )
1301
1507
  return s_bodyContacts.data;
1302
1508
  }
1303
1509
 
1510
+ // Each record's points as [x, y, z] doubles, room for four to a record, beside the float records
1511
+ #define BX_BODY_CONTACT_POINTS_STRIDE ( 3 * B3_MAX_MANIFOLD_POINTS )
1512
+ static bxDoubleBuffer s_bodyContactPoints;
1513
+
1514
+ BX_EXPORT double* bx_BodyContactPointsF64Ptr( void )
1515
+ {
1516
+ return s_bodyContactPoints.data;
1517
+ }
1518
+
1304
1519
  /// The index of shapeId among the body's shapes, or -1.
1305
1520
  static int bxShapeIndex( const bxBody* body, b3ShapeId shapeId )
1306
1521
  {
@@ -1373,8 +1588,10 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1373
1588
  }
1374
1589
  bxFloatBuffer_Reserve( &s_bodyContacts, manifoldTotal * BX_BODY_CONTACT_STRIDE );
1375
1590
 
1591
+ bxDoubleBuffer_Reserve( &s_bodyContactPoints, manifoldTotal * BX_BODY_CONTACT_POINTS_STRIDE );
1376
1592
  b3Pos center = b3Body_GetWorldCenter( body->id );
1377
1593
  float* out = s_bodyContacts.data;
1594
+ double* outPoints = s_bodyContactPoints.data;
1378
1595
  int count = 0;
1379
1596
  for ( int i = 0; i < contactCount; ++i )
1380
1597
  {
@@ -1394,6 +1611,7 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1394
1611
  {
1395
1612
  const b3Manifold* manifold = data->manifolds + m;
1396
1613
  memset( out, 0, BX_BODY_CONTACT_STRIDE * sizeof( float ) );
1614
+ memset( outPoints, 0, BX_BODY_CONTACT_POINTS_STRIDE * sizeof( double ) );
1397
1615
  out[0] = (float)selfIndex;
1398
1616
  out[1] = (float)( other != NULL ? otherSlot : 0 );
1399
1617
  out[2] = (float)otherIndex;
@@ -1414,6 +1632,9 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1414
1632
  po[0] = (float)point.x;
1415
1633
  po[1] = (float)point.y;
1416
1634
  po[2] = (float)point.z;
1635
+ outPoints[3 * p + 0] = point.x;
1636
+ outPoints[3 * p + 1] = point.y;
1637
+ outPoints[3 * p + 2] = point.z;
1417
1638
  po[3] = mp->separation;
1418
1639
  po[4] = mp->normalImpulse;
1419
1640
  po[5] = mp->totalNormalImpulse;
@@ -1422,6 +1643,7 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1422
1643
  po[8] = (float)bxContactMaterial( other, otherIndex, mp->triangleIndex );
1423
1644
  }
1424
1645
  out += BX_BODY_CONTACT_STRIDE;
1646
+ outPoints += BX_BODY_CONTACT_POINTS_STRIDE;
1425
1647
  count += 1;
1426
1648
  }
1427
1649
  }
@@ -1570,6 +1792,40 @@ BX_EXPORT int bx_Body_GetShapeCount( int slot )
1570
1792
  return body->shapeCount;
1571
1793
  }
1572
1794
 
1795
+ /// Gives the body a feature key, an integer from 1 to 2^44 - 1 passed as a double, and each of its shapes, now and when
1796
+ /// it is next shaped, the key (key << 20) | its index in the body. A moving shape's new contacts are made in the order
1797
+ /// of these keys, continuous collision sweeps in that order and a closest cast's tie goes to the lower key, so statics
1798
+ /// keyed by what they are (a cell, a piece) give the same contacts in the same order however and whenever they were
1799
+ /// made. Keys must differ between bodies a moving shape can reach. 0 takes the key off.
1800
+ BX_EXPORT void bx_Body_SetFeatureKey( int slot, double key )
1801
+ {
1802
+ BX_BODY( slot );
1803
+ uint64_t bodyKey = key >= 1.0 && key < 17592186044416.0 ? (uint64_t)key : 0;
1804
+ body->featureKey = bodyKey;
1805
+ for ( int i = 0; i < body->shapeCount; ++i )
1806
+ {
1807
+ if ( b3Shape_IsValid( body->shapes[i] ) )
1808
+ {
1809
+ b3Shape_SetFeatureKey( body->shapes[i], bodyKey == 0 ? 0 : ( bodyKey << BX_FEATURE_INDEX_BITS ) | (uint64_t)i );
1810
+ }
1811
+ }
1812
+ }
1813
+
1814
+ BX_EXPORT double bx_Body_GetFeatureKey( int slot )
1815
+ {
1816
+ BX_BODY_RET( slot, 0.0 );
1817
+ return (double)body->featureKey;
1818
+ }
1819
+
1820
+ static b3ShapeId bxBodyShape( int slot, int index );
1821
+
1822
+ /// Shape `index`'s own feature key, as Box3D orders by it, passed as a double: exact up to 2^53.
1823
+ BX_EXPORT double bx_Body_GetShapeFeatureKey( int slot, int index )
1824
+ {
1825
+ b3ShapeId shapeId = bxBodyShape( slot, index );
1826
+ return B3_IS_NULL( shapeId ) ? 0.0 : (double)b3Shape_GetFeatureKey( shapeId );
1827
+ }
1828
+
1573
1829
  /// Begin/end touch events and hit events are enabled separately so a body only pays for the events it asked for.
1574
1830
  BX_EXPORT void bx_Body_SetEventFlags( int slot, int contactEvents, int hitEvents )
1575
1831
  {
@@ -1614,6 +1870,7 @@ static int bxNewDesc( int kind )
1614
1870
  desc->categoryBits = B3_DEFAULT_CATEGORY_BITS;
1615
1871
  desc->maskBits = B3_DEFAULT_MASK_BITS;
1616
1872
  desc->meshScale = b3Vec3_one;
1873
+ desc->invokeContactCreation = 1;
1617
1874
  return slot;
1618
1875
  }
1619
1876
 
@@ -1762,6 +2019,146 @@ BX_EXPORT int bx_ShapeDesc_CreateHeightField( const float* heights, const unsign
1762
2019
  return slot;
1763
2020
  }
1764
2021
 
2022
+ // Box3D's own allocator (core.c), which b3DestroyMesh and b3DestroyHeightField free with: an imported block must come
2023
+ // from it, with its alignment and its byte count.
2024
+ extern void* b3Alloc( size_t size );
2025
+
2026
+ /// Where a mesh or height field description's data starts in this instance's memory, or 0 for any other description.
2027
+ /// Box3D keeps either as one block addressed by offsets from its start, with no pointer in it, so the block's bytes
2028
+ /// (bx_ShapeDesc_GetGeometryByteCount of them) can be copied out and made into a shape in another instance of the same
2029
+ /// build: a worker's instance with no world builds the geometry, and the simulation's instance only copies it in.
2030
+ BX_EXPORT const void* bx_ShapeDesc_GetGeometryBytes( int slot )
2031
+ {
2032
+ bxShapeDesc* desc = bxGetDesc( slot );
2033
+ if ( desc == NULL )
2034
+ {
2035
+ return NULL;
2036
+ }
2037
+ if ( desc->kind == bx_meshDesc )
2038
+ {
2039
+ return desc->mesh;
2040
+ }
2041
+ if ( desc->kind == bx_heightFieldDesc )
2042
+ {
2043
+ return desc->heightField;
2044
+ }
2045
+ return NULL;
2046
+ }
2047
+
2048
+ /// The byte count of the block bx_ShapeDesc_GetGeometryBytes points at, or 0.
2049
+ BX_EXPORT int bx_ShapeDesc_GetGeometryByteCount( int slot )
2050
+ {
2051
+ bxShapeDesc* desc = bxGetDesc( slot );
2052
+ if ( desc == NULL )
2053
+ {
2054
+ return 0;
2055
+ }
2056
+ if ( desc->kind == bx_meshDesc && desc->mesh != NULL )
2057
+ {
2058
+ return desc->mesh->byteCount;
2059
+ }
2060
+ if ( desc->kind == bx_heightFieldDesc && desc->heightField != NULL )
2061
+ {
2062
+ return desc->heightField->byteCount;
2063
+ }
2064
+ return 0;
2065
+ }
2066
+
2067
+ // Whether an array of count elements of size bytes at offset lies inside a block of byteCount bytes. An offset of 0
2068
+ // means the array is absent.
2069
+ static int bxInBlock( int32_t offset, int32_t count, int size, int byteCount )
2070
+ {
2071
+ if ( offset == 0 )
2072
+ {
2073
+ return 1;
2074
+ }
2075
+ return offset > 0 && count >= 0 && (int64_t)offset + (int64_t)count * size <= byteCount;
2076
+ }
2077
+
2078
+ /// A copy of bytes the caller has put in this instance's memory, in a block from Box3D's allocator, if they are a mesh
2079
+ /// (kind 3) or height field (kind 4) block of this build, whole and with every array inside it. NULL otherwise.
2080
+ static void* bxImportGeometry( const void* bytes, int byteCount, int kind )
2081
+ {
2082
+ if ( bytes == NULL || byteCount < 16 )
2083
+ {
2084
+ return NULL;
2085
+ }
2086
+ if ( kind == bx_meshDesc )
2087
+ {
2088
+ if ( byteCount < (int)sizeof( b3MeshData ) )
2089
+ {
2090
+ return NULL;
2091
+ }
2092
+ b3MeshData header;
2093
+ memcpy( &header, bytes, sizeof( header ) );
2094
+ if ( header.version != B3_MESH_VERSION || header.byteCount != byteCount ||
2095
+ bxInBlock( header.nodeOffset, header.nodeCount, (int)sizeof( b3MeshNode ), byteCount ) == 0 ||
2096
+ bxInBlock( header.vertexOffset, header.vertexCount, (int)sizeof( b3Vec3 ), byteCount ) == 0 ||
2097
+ bxInBlock( header.triangleOffset, header.triangleCount, (int)sizeof( b3MeshTriangle ), byteCount ) == 0 ||
2098
+ bxInBlock( header.materialOffset, header.triangleCount, 1, byteCount ) == 0 ||
2099
+ bxInBlock( header.flagsOffset, header.triangleCount, 1, byteCount ) == 0 )
2100
+ {
2101
+ return NULL;
2102
+ }
2103
+ }
2104
+ else
2105
+ {
2106
+ if ( byteCount < (int)sizeof( b3HeightFieldData ) )
2107
+ {
2108
+ return NULL;
2109
+ }
2110
+ b3HeightFieldData header;
2111
+ memcpy( &header, bytes, sizeof( header ) );
2112
+ int points = header.columnCount * header.rowCount;
2113
+ int cells = ( header.columnCount - 1 ) * ( header.rowCount - 1 );
2114
+ if ( header.version != B3_HEIGHT_FIELD_VERSION || header.byteCount != byteCount || header.columnCount < 2 ||
2115
+ header.rowCount < 2 || bxInBlock( header.heightsOffset, points, 2, byteCount ) == 0 ||
2116
+ bxInBlock( header.materialOffset, cells, 1, byteCount ) == 0 ||
2117
+ bxInBlock( header.flagsOffset, 2 * cells, 1, byteCount ) == 0 )
2118
+ {
2119
+ return NULL;
2120
+ }
2121
+ }
2122
+ void* block = b3Alloc( (size_t)byteCount );
2123
+ if ( block != NULL )
2124
+ {
2125
+ memcpy( block, bytes, (size_t)byteCount );
2126
+ }
2127
+ return block;
2128
+ }
2129
+
2130
+ /// A mesh description from the bytes of a mesh block (bx_ShapeDesc_GetGeometryBytes, from this instance or another of
2131
+ /// the same build) that the caller has put at `bytes`: the block is copied as it is, and nothing in it needs fixing up.
2132
+ /// The scale is the description's, as bx_ShapeDesc_CreateMesh takes it. Returns 0 if the bytes are not such a block.
2133
+ BX_EXPORT int bx_ShapeDesc_CreateMeshFromBytes( const void* bytes, int byteCount, float sx, float sy, float sz )
2134
+ {
2135
+ b3MeshData* mesh = (b3MeshData*)bxImportGeometry( bytes, byteCount, bx_meshDesc );
2136
+ if ( mesh == NULL )
2137
+ {
2138
+ return 0;
2139
+ }
2140
+ int slot = bxNewDesc( bx_meshDesc );
2141
+ s_descs[slot].mesh = mesh;
2142
+ s_descs[slot].meshScale = bxVec3( sx, sy, sz );
2143
+ return slot;
2144
+ }
2145
+
2146
+ /// A height field description from the bytes of a height field block, as bx_ShapeDesc_CreateMeshFromBytes makes a mesh.
2147
+ /// The block carries its scale, quantization and winding; (ox, oy, oz) is the description's local offset, as
2148
+ /// bx_ShapeDesc_CreateHeightField takes it.
2149
+ BX_EXPORT int bx_ShapeDesc_CreateHeightFieldFromBytes( const void* bytes, int byteCount, float ox, float oy, float oz )
2150
+ {
2151
+ b3HeightFieldData* hf = (b3HeightFieldData*)bxImportGeometry( bytes, byteCount, bx_heightFieldDesc );
2152
+ if ( hf == NULL )
2153
+ {
2154
+ return 0;
2155
+ }
2156
+ int slot = bxNewDesc( bx_heightFieldDesc );
2157
+ s_descs[slot].heightField = hf;
2158
+ s_descs[slot].offset = bxVec3( ox, oy, oz );
2159
+ return slot;
2160
+ }
2161
+
1765
2162
  BX_EXPORT int bx_ShapeDesc_CreateContainer( void )
1766
2163
  {
1767
2164
  return bxNewDesc( bx_containerDesc );
@@ -1905,6 +2302,27 @@ BX_EXPORT int bx_ShapeDesc_IsSensor( int slot )
1905
2302
  return desc == NULL ? 0 : desc->isSensor;
1906
2303
  }
1907
2304
 
2305
+ /// Whether a static shape made from this description looks for its own pairs on the next step (1, Box3D's default), or
2306
+ /// waits until a moving shape finds it (0). With 0 a static never enters the broad phase's move buffer, which saves the
2307
+ /// step the pair slots it would reserve for it (Box3D keeps its step stack at 1.5 times the largest it has needed), and
2308
+ /// a static made where a moving shape's fat bounds already reach it is paired only once that shape moves again, so a
2309
+ /// static made late pairs on the same step as one made at the start. Off on a container, it is off for every shape made
2310
+ /// from its children. Dynamic and kinematic shapes always look for pairs.
2311
+ BX_EXPORT void bx_ShapeDesc_SetInvokeContactCreation( int slot, int flag )
2312
+ {
2313
+ bxShapeDesc* desc = bxGetDesc( slot );
2314
+ if ( desc != NULL )
2315
+ {
2316
+ desc->invokeContactCreation = flag != 0;
2317
+ }
2318
+ }
2319
+
2320
+ BX_EXPORT int bx_ShapeDesc_GetInvokeContactCreation( int slot )
2321
+ {
2322
+ bxShapeDesc* desc = bxGetDesc( slot );
2323
+ return desc == NULL ? 0 : desc->invokeContactCreation;
2324
+ }
2325
+
1908
2326
  static b3AABB bxDescAABB( bxShapeDesc* desc, b3Transform xf, b3Vec3 scale, int depth )
1909
2327
  {
1910
2328
  b3AABB aabb = { b3Vec3_zero, b3Vec3_zero };
@@ -2108,6 +2526,10 @@ static void bxBodyPushShape( bxBody* body, b3ShapeId shapeId, int descSlot, int
2108
2526
  body->shapeDescs[body->shapeCount] = descSlot;
2109
2527
  body->geomDescs[body->shapeCount] = geomSlot;
2110
2528
  body->shapeCount += 1;
2529
+ if ( body->clipEnabled )
2530
+ {
2531
+ b3Shape_EnablePreSolveEvents( shapeId, true );
2532
+ }
2111
2533
  bxShapeDesc* geom = bxGetDesc( geomSlot );
2112
2534
  if ( geom != NULL && ( geom->kind == bx_meshDesc || geom->kind == bx_heightFieldDesc ) )
2113
2535
  {
@@ -2165,13 +2587,15 @@ static int bxBakeTransformedMesh( const bxShapeDesc* source, b3Transform xf, b3V
2165
2587
  return slot;
2166
2588
  }
2167
2589
 
2168
- static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 scale, int depth )
2590
+ /// invoke is 0 when a container above this description has contact creation off.
2591
+ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 scale, int depth, int invoke )
2169
2592
  {
2170
2593
  bxShapeDesc* desc = bxGetDesc( descSlot );
2171
2594
  if ( desc == NULL || depth > 8 )
2172
2595
  {
2173
2596
  return;
2174
2597
  }
2598
+ invoke = invoke != 0 && desc->invokeContactCreation != 0;
2175
2599
 
2176
2600
  b3ShapeDef def = b3DefaultShapeDef();
2177
2601
  def.density = desc->density;
@@ -2188,6 +2612,10 @@ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 sc
2188
2612
  def.enableHitEvents = body->hitEvents != 0;
2189
2613
  // the caller applies the mass once after instantiating every shape of the description
2190
2614
  def.updateBodyMass = false;
2615
+ def.invokeContactCreation = invoke != 0;
2616
+ // the shape takes the next index on the body, whichever Box3D body it is made on; a body without a key leaves its
2617
+ // shapes at zero, so they fall back to their ids as unkeyed shapes do
2618
+ def.featureKey = body->featureKey == 0 ? 0 : ( body->featureKey << BX_FEATURE_INDEX_BITS ) | (uint64_t)body->shapeCount;
2191
2619
 
2192
2620
  switch ( desc->kind )
2193
2621
  {
@@ -2265,7 +2693,7 @@ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 sc
2265
2693
  body->helperCapacity = capacity;
2266
2694
  }
2267
2695
  b3WorldTransform bodyXf = b3Body_GetTransform( body->id );
2268
- b3Transform world = b3MulTransforms( bodyXf, local );
2696
+ b3WorldTransform world = b3MulWorldTransforms( bodyXf, local );
2269
2697
  b3BodyDef helperDef = b3DefaultBodyDef();
2270
2698
  helperDef.type = b3_staticBody;
2271
2699
  helperDef.position = world.p;
@@ -2291,7 +2719,8 @@ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 sc
2291
2719
  break;
2292
2720
  }
2293
2721
  bxChild child = container->children[i];
2294
- bxInstantiate( body, child.desc, b3MulTransforms( xf, child.transform ), bxMulScale( scale, child.scale ), depth + 1 );
2722
+ bxInstantiate( body, child.desc, b3MulTransforms( xf, child.transform ), bxMulScale( scale, child.scale ), depth + 1,
2723
+ invoke );
2295
2724
  }
2296
2725
  break;
2297
2726
  }
@@ -2308,7 +2737,7 @@ BX_EXPORT void bx_Body_SetShape( int slot, int descSlot )
2308
2737
  body->shapeDesc = descSlot;
2309
2738
  if ( descSlot != 0 )
2310
2739
  {
2311
- bxInstantiate( body, descSlot, b3Transform_identity, b3Vec3_one, 0 );
2740
+ bxInstantiate( body, descSlot, b3Transform_identity, b3Vec3_one, 0, 1 );
2312
2741
  }
2313
2742
  b3Body_ApplyMassFromShapes( body->id );
2314
2743
  }
@@ -3234,8 +3663,27 @@ typedef struct bxRayContext
3234
3663
  int hitSensors;
3235
3664
  int closestOnly;
3236
3665
  int count;
3666
+ // the closest hit so far, for ties
3667
+ uint64_t bestKey;
3668
+ int bestBody;
3669
+ int bestIndex;
3237
3670
  } bxRayContext;
3238
3671
 
3672
+ /// Whether a hit on (key, body, index) wins a tie against the closest so far: the lower feature key, then the lower body
3673
+ /// slot and shape index, so a tie does not go to whichever shape the broad phase's tree happened to give first.
3674
+ static int bxHitPrecedes( uint64_t key, int body, int index, uint64_t bestKey, int bestBody, int bestIndex )
3675
+ {
3676
+ if ( key != bestKey )
3677
+ {
3678
+ return key < bestKey;
3679
+ }
3680
+ if ( body != bestBody )
3681
+ {
3682
+ return body < bestBody;
3683
+ }
3684
+ return index < bestIndex;
3685
+ }
3686
+
3239
3687
  static bxFloatBuffer s_rayHits;
3240
3688
  #define BX_RAY_HIT_STRIDE 11
3241
3689
 
@@ -3244,6 +3692,22 @@ BX_EXPORT float* bx_RayHitsPtr( void )
3244
3692
  return s_rayHits.data;
3245
3693
  }
3246
3694
 
3695
+ // Each hit's point as [x, y, z] doubles, beside its float record
3696
+ static bxDoubleBuffer s_rayHitPoints;
3697
+
3698
+ BX_EXPORT double* bx_RayHitPointsF64Ptr( void )
3699
+ {
3700
+ return s_rayHitPoints.data;
3701
+ }
3702
+
3703
+ static void bxWriteHitPoint( int index, b3Pos point )
3704
+ {
3705
+ bxDoubleBuffer_Reserve( &s_rayHitPoints, ( index + 1 ) * 3 );
3706
+ s_rayHitPoints.data[3 * index + 0] = point.x;
3707
+ s_rayHitPoints.data[3 * index + 1] = point.y;
3708
+ s_rayHitPoints.data[3 * index + 2] = point.z;
3709
+ }
3710
+
3247
3711
  static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, int triangleIndex,
3248
3712
  int childIndex, void* context )
3249
3713
  {
@@ -3259,6 +3723,26 @@ static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float
3259
3723
  {
3260
3724
  return -1.0f;
3261
3725
  }
3726
+ int shapeIndex = bxShapeIndex( s_bodies + bodySlot, shapeId );
3727
+ // The ray is clipped just past the closest hit, not at it, so a shape that ties with it is still reported, and the
3728
+ // tie goes by feature key.
3729
+ float clip = b3MinFloat( nextafterf( fraction, 2.0f ), 1.0f );
3730
+ if ( ctx->closestOnly )
3731
+ {
3732
+ uint64_t key = b3Shape_GetFeatureKey( shapeId );
3733
+ if ( ctx->count > 0 )
3734
+ {
3735
+ float best = s_rayHits.data[6];
3736
+ if ( fraction > best ||
3737
+ ( fraction == best && bxHitPrecedes( key, bodySlot, shapeIndex, ctx->bestKey, ctx->bestBody, ctx->bestIndex ) == 0 ) )
3738
+ {
3739
+ return b3MinFloat( nextafterf( best, 2.0f ), 1.0f );
3740
+ }
3741
+ }
3742
+ ctx->bestKey = key;
3743
+ ctx->bestBody = bodySlot;
3744
+ ctx->bestIndex = shapeIndex;
3745
+ }
3262
3746
  int index = ctx->closestOnly ? 0 : ctx->count;
3263
3747
  bxFloatBuffer_Reserve( &s_rayHits, ( index + 1 ) * BX_RAY_HIT_STRIDE );
3264
3748
  float* out = s_rayHits.data + index * BX_RAY_HIT_STRIDE;
@@ -3272,14 +3756,15 @@ static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float
3272
3756
  out[7] = (float)bodySlot;
3273
3757
  out[8] = (float)bxDescSlotFromShape( shapeId );
3274
3758
  out[9] = (float)triangleIndex;
3275
- out[10] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
3759
+ out[10] = (float)shapeIndex;
3760
+ bxWriteHitPoint( index, point );
3276
3761
  ctx->count = ctx->closestOnly ? 1 : ctx->count + 1;
3277
- return ctx->closestOnly ? fraction : 1.0f;
3762
+ return ctx->closestOnly ? clip : 1.0f;
3278
3763
  }
3279
3764
 
3280
3765
  /// Casts a ray from origin along translation. Returns the number of hits written to bx_RayHitsPtr().
3281
3766
  /// Each hit: [px, py, pz, nx, ny, nz, fraction, bodySlot, shapeDesc, triangleIndex, shapeIndex]
3282
- BX_EXPORT int bx_World_CastRay( int w, float ox, float oy, float oz, float dx, float dy, float dz, unsigned int categoryBits,
3767
+ BX_EXPORT int bx_World_CastRay( int w, double ox, double oy, double oz, float dx, float dy, float dz, unsigned int categoryBits,
3283
3768
  unsigned int maskBits, int ignoreBody, int hitSensors, int closestOnly )
3284
3769
  {
3285
3770
  b3WorldId worldId = bxGetWorld( w );
@@ -3287,21 +3772,14 @@ BX_EXPORT int bx_World_CastRay( int w, float ox, float oy, float oz, float dx, f
3287
3772
  {
3288
3773
  return 0;
3289
3774
  }
3290
- bxRayContext ctx = { ignoreBody, hitSensors, closestOnly, 0 };
3775
+ bxRayContext ctx = { ignoreBody, hitSensors, closestOnly, 0, 0, 0, 0 };
3291
3776
  b3QueryFilter filter = b3DefaultQueryFilter();
3292
3777
  filter.categoryBits = (uint64_t)categoryBits;
3293
3778
  filter.maskBits = (uint64_t)maskBits;
3294
- b3World_CastRay( worldId, bxVec3( ox, oy, oz ), bxVec3( dx, dy, dz ), filter, bxRayCallback, &ctx );
3779
+ b3World_CastRay( worldId, bxPos( ox, oy, oz ), bxVec3( dx, dy, dz ), filter, bxRayCallback, &ctx );
3295
3780
  return ctx.count;
3296
3781
  }
3297
3782
 
3298
- /// A world position from three doubles: exact in a double precision build, rounded to float in the default one.
3299
- static b3Pos bxPos( double x, double y, double z )
3300
- {
3301
- b3Pos p = { x, y, z };
3302
- return p;
3303
- }
3304
-
3305
3783
  typedef struct bxShapeCastContext
3306
3784
  {
3307
3785
  int ignoreBody;
@@ -3309,6 +3787,10 @@ typedef struct bxShapeCastContext
3309
3787
  int count;
3310
3788
  float radius;
3311
3789
  b3Vec3 translation;
3790
+ // the closest hit so far, for ties
3791
+ uint64_t bestKey;
3792
+ int bestBody;
3793
+ int bestIndex;
3312
3794
  } bxShapeCastContext;
3313
3795
 
3314
3796
  // Box3D's shape cast stops where the two cores are max(slop, radii - slop) apart, so that a body moved to the hit is
@@ -3354,12 +3836,20 @@ static float bxShapeCastCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal,
3354
3836
  touch = b3MaxFloat( touch, 0.0f );
3355
3837
 
3356
3838
  int index = ctx->count;
3839
+ int shapeIndex = bxShapeIndex( s_bodies + bodySlot, shapeId );
3357
3840
  if ( ctx->closestOnly )
3358
3841
  {
3359
- if ( ctx->count > 0 && s_rayHits.data[6] <= touch )
3842
+ // a tie goes by feature key, not to whichever shape the tree gave first
3843
+ uint64_t key = b3Shape_GetFeatureKey( shapeId );
3844
+ if ( ctx->count > 0 &&
3845
+ ( s_rayHits.data[6] < touch ||
3846
+ ( s_rayHits.data[6] == touch && bxHitPrecedes( key, bodySlot, shapeIndex, ctx->bestKey, ctx->bestBody, ctx->bestIndex ) == 0 ) ) )
3360
3847
  {
3361
3848
  return 1.0f;
3362
3849
  }
3850
+ ctx->bestKey = key;
3851
+ ctx->bestBody = bodySlot;
3852
+ ctx->bestIndex = shapeIndex;
3363
3853
  index = 0;
3364
3854
  }
3365
3855
  bxFloatBuffer_Reserve( &s_rayHits, ( index + 1 ) * BX_RAY_HIT_STRIDE );
@@ -3374,7 +3864,8 @@ static float bxShapeCastCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal,
3374
3864
  out[7] = (float)bodySlot;
3375
3865
  out[8] = (float)bxDescSlotFromShape( shapeId );
3376
3866
  out[9] = (float)triangleIndex;
3377
- out[10] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
3867
+ out[10] = (float)shapeIndex;
3868
+ bxWriteHitPoint( index, point );
3378
3869
  ctx->count = ctx->closestOnly ? 1 : ctx->count + 1;
3379
3870
  return 1.0f;
3380
3871
  }
@@ -3391,7 +3882,7 @@ BX_EXPORT int bx_World_CastShape( int w, double ox, double oy, double oz, const
3391
3882
  {
3392
3883
  return 0;
3393
3884
  }
3394
- bxShapeCastContext ctx = { ignoreBody, closestOnly, 0, radius, bxVec3( dx, dy, dz ) };
3885
+ bxShapeCastContext ctx = { ignoreBody, closestOnly, 0, radius, bxVec3( dx, dy, dz ), 0, 0, 0 };
3395
3886
  b3QueryFilter filter = b3DefaultQueryFilter();
3396
3887
  filter.categoryBits = (uint64_t)categoryBits;
3397
3888
  filter.maskBits = (uint64_t)maskBits;