babylon-box3d 0.6.0 → 0.7.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.
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,13 @@ 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;
109
160
  } bxBody;
110
161
 
162
+ #define BX_FEATURE_INDEX_BITS 20
163
+
111
164
  static void bxReleaseBodyShapes( bxBody* body, int destroyShapes );
112
165
 
113
166
  static bxBody* s_bodies;
@@ -154,6 +207,10 @@ typedef struct bxShapeDesc
154
207
  int groupIndex;
155
208
  float rollingResistance;
156
209
  int isSensor;
210
+ // Box3D's b3ShapeDef::invokeContactCreation, on by default: a static shape made with it starts in the broad phase's
211
+ // move buffer and looks for its own pairs on the next step. Off, it waits to be found by a moving shape. A container
212
+ // that has it off turns it off for every shape made from its children.
213
+ int invokeContactCreation;
157
214
  bxChild* children;
158
215
  int childCount;
159
216
  int childCapacity;
@@ -685,7 +742,7 @@ BX_EXPORT void bx_World_SetContactTuning( int w, float hertz, float dampingRatio
685
742
  }
686
743
  }
687
744
 
688
- BX_EXPORT void bx_World_Explode( int w, float px, float py, float pz, float radius, float falloff, float impulsePerArea )
745
+ BX_EXPORT void bx_World_Explode( int w, double px, double py, double pz, float radius, float falloff, float impulsePerArea )
689
746
  {
690
747
  b3WorldId worldId = bxGetWorld( w );
691
748
  if ( B3_IS_NULL( worldId ) )
@@ -693,7 +750,7 @@ BX_EXPORT void bx_World_Explode( int w, float px, float py, float pz, float radi
693
750
  return;
694
751
  }
695
752
  b3ExplosionDef def = b3DefaultExplosionDef();
696
- def.position = bxVec3( px, py, pz );
753
+ def.position = bxPos( px, py, pz );
697
754
  def.radius = radius;
698
755
  def.falloff = falloff;
699
756
  def.impulsePerArea = impulsePerArea;
@@ -714,6 +771,13 @@ BX_EXPORT int bx_World_GetJointEventCount( int w )
714
771
  return B3_IS_NON_NULL( worldId ) ? b3World_GetJointEvents( worldId ).count : 0;
715
772
  }
716
773
 
774
+ /// 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.
775
+ BX_EXPORT int bx_World_GetStackUsed( int w )
776
+ {
777
+ b3WorldId worldId = bxGetWorld( w );
778
+ return B3_IS_NON_NULL( worldId ) ? b3World_GetCounters( worldId ).stackUsed : 0;
779
+ }
780
+
717
781
  /// scratch: [bodyCount, shapeCount, contactCount, jointCount, islandCount, stepMs, collideMs, solveMs]
718
782
  BX_EXPORT void bx_World_GetStats( int w )
719
783
  {
@@ -985,7 +1049,7 @@ BX_EXPORT int bx_World_GetSensorEvents( int w )
985
1049
  // ---------------------------------------------------------------------------------------------
986
1050
 
987
1051
  /// 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 )
1052
+ 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
1053
  {
990
1054
  b3WorldId worldId = bxGetWorld( w );
991
1055
  if ( B3_IS_NULL( worldId ) )
@@ -996,7 +1060,7 @@ BX_EXPORT int bx_CreateBody( int w, int type, float px, float py, float pz, floa
996
1060
  bxBody* body = s_bodies + slot;
997
1061
  b3BodyDef def = b3DefaultBodyDef();
998
1062
  def.type = (b3BodyType)type;
999
- def.position = bxVec3( px, py, pz );
1063
+ def.position = bxPos( px, py, pz );
1000
1064
  def.rotation = bxQuat( qx, qy, qz, qw );
1001
1065
  def.isAwake = isAwake != 0;
1002
1066
  def.userData = (void*)(intptr_t)slot;
@@ -1092,37 +1156,47 @@ BX_EXPORT int bx_Body_GetType( int slot )
1092
1156
  return (int)b3Body_GetType( body->id );
1093
1157
  }
1094
1158
 
1095
- BX_EXPORT void bx_Body_SetTransform( int slot, float px, float py, float pz, float qx, float qy, float qz, float qw )
1159
+ BX_EXPORT void bx_Body_SetTransform( int slot, double px, double py, double pz, float qx, float qy, float qz, float qw )
1096
1160
  {
1097
1161
  BX_BODY( slot );
1098
- b3Body_SetTransform( body->id, bxVec3( px, py, pz ), bxQuat( qx, qy, qz, qw ) );
1162
+ b3Body_SetTransform( body->id, bxPos( px, py, pz ), bxQuat( qx, qy, qz, qw ) );
1099
1163
  if ( body->helperCount > 0 )
1100
1164
  {
1101
- b3Transform xf = bxTransform( px, py, pz, qx, qy, qz, qw );
1165
+ b3WorldTransform xf;
1166
+ xf.p = bxPos( px, py, pz );
1167
+ xf.q = bxTransform( 0.0f, 0.0f, 0.0f, qx, qy, qz, qw ).q;
1102
1168
  for ( int i = 0; i < body->helperCount; ++i )
1103
1169
  {
1104
1170
  if ( b3Body_IsValid( body->helpers[i].id ) )
1105
1171
  {
1106
- b3Transform h = b3MulTransforms( xf, body->helpers[i].local );
1172
+ b3WorldTransform h = b3MulWorldTransforms( xf, body->helpers[i].local );
1107
1173
  b3Body_SetTransform( body->helpers[i].id, h.p, h.q );
1108
1174
  }
1109
1175
  }
1110
1176
  }
1111
1177
  }
1112
1178
 
1113
- /// scratch: [px, py, pz, qx, qy, qz, qw]
1179
+ /// scratch: [px, py, pz, qx, qy, qz, qw]; scratchF64: [px, py, pz], the position as the build holds it
1114
1180
  BX_EXPORT void bx_Body_GetTransform( int slot )
1115
1181
  {
1116
1182
  BX_BODY( slot );
1117
1183
  b3WorldTransform xf = b3Body_GetTransform( body->id );
1118
- bxWriteVec3( 0, xf.p );
1184
+ s_scratch[0] = (float)xf.p.x;
1185
+ s_scratch[1] = (float)xf.p.y;
1186
+ s_scratch[2] = (float)xf.p.z;
1119
1187
  bxWriteQuat( 3, xf.q );
1188
+ s_scratchF64[0] = xf.p.x;
1189
+ s_scratchF64[1] = xf.p.y;
1190
+ s_scratchF64[2] = xf.p.z;
1120
1191
  }
1121
1192
 
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 )
1193
+ BX_EXPORT void bx_Body_SetTargetTransform( int slot, double px, double py, double pz, float qx, float qy, float qz, float qw,
1194
+ float dt )
1123
1195
  {
1124
1196
  BX_BODY( slot );
1125
- b3WorldTransform xf = bxTransform( px, py, pz, qx, qy, qz, qw );
1197
+ b3WorldTransform xf;
1198
+ xf.p = bxPos( px, py, pz );
1199
+ xf.q = bxTransform( 0.0f, 0.0f, 0.0f, qx, qy, qz, qw ).q;
1126
1200
  b3Body_SetTargetTransform( body->id, xf, dt, true );
1127
1201
  }
1128
1202
 
@@ -1186,10 +1260,10 @@ BX_EXPORT float bx_Body_GetGravityScale( int slot )
1186
1260
  return b3Body_GetGravityScale( body->id );
1187
1261
  }
1188
1262
 
1189
- BX_EXPORT void bx_Body_ApplyForce( int slot, float fx, float fy, float fz, float px, float py, float pz )
1263
+ BX_EXPORT void bx_Body_ApplyForce( int slot, float fx, float fy, float fz, double px, double py, double pz )
1190
1264
  {
1191
1265
  BX_BODY( slot );
1192
- b3Body_ApplyForce( body->id, bxVec3( fx, fy, fz ), bxVec3( px, py, pz ), true );
1266
+ b3Body_ApplyForce( body->id, bxVec3( fx, fy, fz ), bxPos( px, py, pz ), true );
1193
1267
  }
1194
1268
 
1195
1269
  BX_EXPORT void bx_Body_ApplyForceToCenter( int slot, float fx, float fy, float fz )
@@ -1204,10 +1278,10 @@ BX_EXPORT void bx_Body_ApplyTorque( int slot, float x, float y, float z )
1204
1278
  b3Body_ApplyTorque( body->id, bxVec3( x, y, z ), true );
1205
1279
  }
1206
1280
 
1207
- BX_EXPORT void bx_Body_ApplyLinearImpulse( int slot, float ix, float iy, float iz, float px, float py, float pz )
1281
+ BX_EXPORT void bx_Body_ApplyLinearImpulse( int slot, float ix, float iy, float iz, double px, double py, double pz )
1208
1282
  {
1209
1283
  BX_BODY( slot );
1210
- b3Body_ApplyLinearImpulse( body->id, bxVec3( ix, iy, iz ), bxVec3( px, py, pz ), true );
1284
+ b3Body_ApplyLinearImpulse( body->id, bxVec3( ix, iy, iz ), bxPos( px, py, pz ), true );
1211
1285
  }
1212
1286
 
1213
1287
  BX_EXPORT void bx_Body_ApplyLinearImpulseToCenter( int slot, float ix, float iy, float iz )
@@ -1301,6 +1375,15 @@ BX_EXPORT float* bx_BodyContactsPtr( void )
1301
1375
  return s_bodyContacts.data;
1302
1376
  }
1303
1377
 
1378
+ // Each record's points as [x, y, z] doubles, room for four to a record, beside the float records
1379
+ #define BX_BODY_CONTACT_POINTS_STRIDE ( 3 * B3_MAX_MANIFOLD_POINTS )
1380
+ static bxDoubleBuffer s_bodyContactPoints;
1381
+
1382
+ BX_EXPORT double* bx_BodyContactPointsF64Ptr( void )
1383
+ {
1384
+ return s_bodyContactPoints.data;
1385
+ }
1386
+
1304
1387
  /// The index of shapeId among the body's shapes, or -1.
1305
1388
  static int bxShapeIndex( const bxBody* body, b3ShapeId shapeId )
1306
1389
  {
@@ -1373,8 +1456,10 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1373
1456
  }
1374
1457
  bxFloatBuffer_Reserve( &s_bodyContacts, manifoldTotal * BX_BODY_CONTACT_STRIDE );
1375
1458
 
1459
+ bxDoubleBuffer_Reserve( &s_bodyContactPoints, manifoldTotal * BX_BODY_CONTACT_POINTS_STRIDE );
1376
1460
  b3Pos center = b3Body_GetWorldCenter( body->id );
1377
1461
  float* out = s_bodyContacts.data;
1462
+ double* outPoints = s_bodyContactPoints.data;
1378
1463
  int count = 0;
1379
1464
  for ( int i = 0; i < contactCount; ++i )
1380
1465
  {
@@ -1394,6 +1479,7 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1394
1479
  {
1395
1480
  const b3Manifold* manifold = data->manifolds + m;
1396
1481
  memset( out, 0, BX_BODY_CONTACT_STRIDE * sizeof( float ) );
1482
+ memset( outPoints, 0, BX_BODY_CONTACT_POINTS_STRIDE * sizeof( double ) );
1397
1483
  out[0] = (float)selfIndex;
1398
1484
  out[1] = (float)( other != NULL ? otherSlot : 0 );
1399
1485
  out[2] = (float)otherIndex;
@@ -1414,6 +1500,9 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1414
1500
  po[0] = (float)point.x;
1415
1501
  po[1] = (float)point.y;
1416
1502
  po[2] = (float)point.z;
1503
+ outPoints[3 * p + 0] = point.x;
1504
+ outPoints[3 * p + 1] = point.y;
1505
+ outPoints[3 * p + 2] = point.z;
1417
1506
  po[3] = mp->separation;
1418
1507
  po[4] = mp->normalImpulse;
1419
1508
  po[5] = mp->totalNormalImpulse;
@@ -1422,6 +1511,7 @@ BX_EXPORT int bx_Body_GetContacts( int slot )
1422
1511
  po[8] = (float)bxContactMaterial( other, otherIndex, mp->triangleIndex );
1423
1512
  }
1424
1513
  out += BX_BODY_CONTACT_STRIDE;
1514
+ outPoints += BX_BODY_CONTACT_POINTS_STRIDE;
1425
1515
  count += 1;
1426
1516
  }
1427
1517
  }
@@ -1570,6 +1660,40 @@ BX_EXPORT int bx_Body_GetShapeCount( int slot )
1570
1660
  return body->shapeCount;
1571
1661
  }
1572
1662
 
1663
+ /// 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
1664
+ /// it is next shaped, the key (key << 20) | its index in the body. A moving shape's new contacts are made in the order
1665
+ /// of these keys, continuous collision sweeps in that order and a closest cast's tie goes to the lower key, so statics
1666
+ /// keyed by what they are (a cell, a piece) give the same contacts in the same order however and whenever they were
1667
+ /// made. Keys must differ between bodies a moving shape can reach. 0 takes the key off.
1668
+ BX_EXPORT void bx_Body_SetFeatureKey( int slot, double key )
1669
+ {
1670
+ BX_BODY( slot );
1671
+ uint64_t bodyKey = key >= 1.0 && key < 17592186044416.0 ? (uint64_t)key : 0;
1672
+ body->featureKey = bodyKey;
1673
+ for ( int i = 0; i < body->shapeCount; ++i )
1674
+ {
1675
+ if ( b3Shape_IsValid( body->shapes[i] ) )
1676
+ {
1677
+ b3Shape_SetFeatureKey( body->shapes[i], bodyKey == 0 ? 0 : ( bodyKey << BX_FEATURE_INDEX_BITS ) | (uint64_t)i );
1678
+ }
1679
+ }
1680
+ }
1681
+
1682
+ BX_EXPORT double bx_Body_GetFeatureKey( int slot )
1683
+ {
1684
+ BX_BODY_RET( slot, 0.0 );
1685
+ return (double)body->featureKey;
1686
+ }
1687
+
1688
+ static b3ShapeId bxBodyShape( int slot, int index );
1689
+
1690
+ /// Shape `index`'s own feature key, as Box3D orders by it, passed as a double: exact up to 2^53.
1691
+ BX_EXPORT double bx_Body_GetShapeFeatureKey( int slot, int index )
1692
+ {
1693
+ b3ShapeId shapeId = bxBodyShape( slot, index );
1694
+ return B3_IS_NULL( shapeId ) ? 0.0 : (double)b3Shape_GetFeatureKey( shapeId );
1695
+ }
1696
+
1573
1697
  /// Begin/end touch events and hit events are enabled separately so a body only pays for the events it asked for.
1574
1698
  BX_EXPORT void bx_Body_SetEventFlags( int slot, int contactEvents, int hitEvents )
1575
1699
  {
@@ -1614,6 +1738,7 @@ static int bxNewDesc( int kind )
1614
1738
  desc->categoryBits = B3_DEFAULT_CATEGORY_BITS;
1615
1739
  desc->maskBits = B3_DEFAULT_MASK_BITS;
1616
1740
  desc->meshScale = b3Vec3_one;
1741
+ desc->invokeContactCreation = 1;
1617
1742
  return slot;
1618
1743
  }
1619
1744
 
@@ -1762,6 +1887,146 @@ BX_EXPORT int bx_ShapeDesc_CreateHeightField( const float* heights, const unsign
1762
1887
  return slot;
1763
1888
  }
1764
1889
 
1890
+ // Box3D's own allocator (core.c), which b3DestroyMesh and b3DestroyHeightField free with: an imported block must come
1891
+ // from it, with its alignment and its byte count.
1892
+ extern void* b3Alloc( size_t size );
1893
+
1894
+ /// Where a mesh or height field description's data starts in this instance's memory, or 0 for any other description.
1895
+ /// Box3D keeps either as one block addressed by offsets from its start, with no pointer in it, so the block's bytes
1896
+ /// (bx_ShapeDesc_GetGeometryByteCount of them) can be copied out and made into a shape in another instance of the same
1897
+ /// build: a worker's instance with no world builds the geometry, and the simulation's instance only copies it in.
1898
+ BX_EXPORT const void* bx_ShapeDesc_GetGeometryBytes( int slot )
1899
+ {
1900
+ bxShapeDesc* desc = bxGetDesc( slot );
1901
+ if ( desc == NULL )
1902
+ {
1903
+ return NULL;
1904
+ }
1905
+ if ( desc->kind == bx_meshDesc )
1906
+ {
1907
+ return desc->mesh;
1908
+ }
1909
+ if ( desc->kind == bx_heightFieldDesc )
1910
+ {
1911
+ return desc->heightField;
1912
+ }
1913
+ return NULL;
1914
+ }
1915
+
1916
+ /// The byte count of the block bx_ShapeDesc_GetGeometryBytes points at, or 0.
1917
+ BX_EXPORT int bx_ShapeDesc_GetGeometryByteCount( int slot )
1918
+ {
1919
+ bxShapeDesc* desc = bxGetDesc( slot );
1920
+ if ( desc == NULL )
1921
+ {
1922
+ return 0;
1923
+ }
1924
+ if ( desc->kind == bx_meshDesc && desc->mesh != NULL )
1925
+ {
1926
+ return desc->mesh->byteCount;
1927
+ }
1928
+ if ( desc->kind == bx_heightFieldDesc && desc->heightField != NULL )
1929
+ {
1930
+ return desc->heightField->byteCount;
1931
+ }
1932
+ return 0;
1933
+ }
1934
+
1935
+ // Whether an array of count elements of size bytes at offset lies inside a block of byteCount bytes. An offset of 0
1936
+ // means the array is absent.
1937
+ static int bxInBlock( int32_t offset, int32_t count, int size, int byteCount )
1938
+ {
1939
+ if ( offset == 0 )
1940
+ {
1941
+ return 1;
1942
+ }
1943
+ return offset > 0 && count >= 0 && (int64_t)offset + (int64_t)count * size <= byteCount;
1944
+ }
1945
+
1946
+ /// 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
1947
+ /// (kind 3) or height field (kind 4) block of this build, whole and with every array inside it. NULL otherwise.
1948
+ static void* bxImportGeometry( const void* bytes, int byteCount, int kind )
1949
+ {
1950
+ if ( bytes == NULL || byteCount < 16 )
1951
+ {
1952
+ return NULL;
1953
+ }
1954
+ if ( kind == bx_meshDesc )
1955
+ {
1956
+ if ( byteCount < (int)sizeof( b3MeshData ) )
1957
+ {
1958
+ return NULL;
1959
+ }
1960
+ b3MeshData header;
1961
+ memcpy( &header, bytes, sizeof( header ) );
1962
+ if ( header.version != B3_MESH_VERSION || header.byteCount != byteCount ||
1963
+ bxInBlock( header.nodeOffset, header.nodeCount, (int)sizeof( b3MeshNode ), byteCount ) == 0 ||
1964
+ bxInBlock( header.vertexOffset, header.vertexCount, (int)sizeof( b3Vec3 ), byteCount ) == 0 ||
1965
+ bxInBlock( header.triangleOffset, header.triangleCount, (int)sizeof( b3MeshTriangle ), byteCount ) == 0 ||
1966
+ bxInBlock( header.materialOffset, header.triangleCount, 1, byteCount ) == 0 ||
1967
+ bxInBlock( header.flagsOffset, header.triangleCount, 1, byteCount ) == 0 )
1968
+ {
1969
+ return NULL;
1970
+ }
1971
+ }
1972
+ else
1973
+ {
1974
+ if ( byteCount < (int)sizeof( b3HeightFieldData ) )
1975
+ {
1976
+ return NULL;
1977
+ }
1978
+ b3HeightFieldData header;
1979
+ memcpy( &header, bytes, sizeof( header ) );
1980
+ int points = header.columnCount * header.rowCount;
1981
+ int cells = ( header.columnCount - 1 ) * ( header.rowCount - 1 );
1982
+ if ( header.version != B3_HEIGHT_FIELD_VERSION || header.byteCount != byteCount || header.columnCount < 2 ||
1983
+ header.rowCount < 2 || bxInBlock( header.heightsOffset, points, 2, byteCount ) == 0 ||
1984
+ bxInBlock( header.materialOffset, cells, 1, byteCount ) == 0 ||
1985
+ bxInBlock( header.flagsOffset, 2 * cells, 1, byteCount ) == 0 )
1986
+ {
1987
+ return NULL;
1988
+ }
1989
+ }
1990
+ void* block = b3Alloc( (size_t)byteCount );
1991
+ if ( block != NULL )
1992
+ {
1993
+ memcpy( block, bytes, (size_t)byteCount );
1994
+ }
1995
+ return block;
1996
+ }
1997
+
1998
+ /// A mesh description from the bytes of a mesh block (bx_ShapeDesc_GetGeometryBytes, from this instance or another of
1999
+ /// the same build) that the caller has put at `bytes`: the block is copied as it is, and nothing in it needs fixing up.
2000
+ /// The scale is the description's, as bx_ShapeDesc_CreateMesh takes it. Returns 0 if the bytes are not such a block.
2001
+ BX_EXPORT int bx_ShapeDesc_CreateMeshFromBytes( const void* bytes, int byteCount, float sx, float sy, float sz )
2002
+ {
2003
+ b3MeshData* mesh = (b3MeshData*)bxImportGeometry( bytes, byteCount, bx_meshDesc );
2004
+ if ( mesh == NULL )
2005
+ {
2006
+ return 0;
2007
+ }
2008
+ int slot = bxNewDesc( bx_meshDesc );
2009
+ s_descs[slot].mesh = mesh;
2010
+ s_descs[slot].meshScale = bxVec3( sx, sy, sz );
2011
+ return slot;
2012
+ }
2013
+
2014
+ /// A height field description from the bytes of a height field block, as bx_ShapeDesc_CreateMeshFromBytes makes a mesh.
2015
+ /// The block carries its scale, quantization and winding; (ox, oy, oz) is the description's local offset, as
2016
+ /// bx_ShapeDesc_CreateHeightField takes it.
2017
+ BX_EXPORT int bx_ShapeDesc_CreateHeightFieldFromBytes( const void* bytes, int byteCount, float ox, float oy, float oz )
2018
+ {
2019
+ b3HeightFieldData* hf = (b3HeightFieldData*)bxImportGeometry( bytes, byteCount, bx_heightFieldDesc );
2020
+ if ( hf == NULL )
2021
+ {
2022
+ return 0;
2023
+ }
2024
+ int slot = bxNewDesc( bx_heightFieldDesc );
2025
+ s_descs[slot].heightField = hf;
2026
+ s_descs[slot].offset = bxVec3( ox, oy, oz );
2027
+ return slot;
2028
+ }
2029
+
1765
2030
  BX_EXPORT int bx_ShapeDesc_CreateContainer( void )
1766
2031
  {
1767
2032
  return bxNewDesc( bx_containerDesc );
@@ -1905,6 +2170,27 @@ BX_EXPORT int bx_ShapeDesc_IsSensor( int slot )
1905
2170
  return desc == NULL ? 0 : desc->isSensor;
1906
2171
  }
1907
2172
 
2173
+ /// Whether a static shape made from this description looks for its own pairs on the next step (1, Box3D's default), or
2174
+ /// waits until a moving shape finds it (0). With 0 a static never enters the broad phase's move buffer, which saves the
2175
+ /// step the pair slots it would reserve for it (Box3D keeps its step stack at 1.5 times the largest it has needed), and
2176
+ /// a static made where a moving shape's fat bounds already reach it is paired only once that shape moves again, so a
2177
+ /// 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
2178
+ /// from its children. Dynamic and kinematic shapes always look for pairs.
2179
+ BX_EXPORT void bx_ShapeDesc_SetInvokeContactCreation( int slot, int flag )
2180
+ {
2181
+ bxShapeDesc* desc = bxGetDesc( slot );
2182
+ if ( desc != NULL )
2183
+ {
2184
+ desc->invokeContactCreation = flag != 0;
2185
+ }
2186
+ }
2187
+
2188
+ BX_EXPORT int bx_ShapeDesc_GetInvokeContactCreation( int slot )
2189
+ {
2190
+ bxShapeDesc* desc = bxGetDesc( slot );
2191
+ return desc == NULL ? 0 : desc->invokeContactCreation;
2192
+ }
2193
+
1908
2194
  static b3AABB bxDescAABB( bxShapeDesc* desc, b3Transform xf, b3Vec3 scale, int depth )
1909
2195
  {
1910
2196
  b3AABB aabb = { b3Vec3_zero, b3Vec3_zero };
@@ -2165,13 +2451,15 @@ static int bxBakeTransformedMesh( const bxShapeDesc* source, b3Transform xf, b3V
2165
2451
  return slot;
2166
2452
  }
2167
2453
 
2168
- static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 scale, int depth )
2454
+ /// invoke is 0 when a container above this description has contact creation off.
2455
+ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 scale, int depth, int invoke )
2169
2456
  {
2170
2457
  bxShapeDesc* desc = bxGetDesc( descSlot );
2171
2458
  if ( desc == NULL || depth > 8 )
2172
2459
  {
2173
2460
  return;
2174
2461
  }
2462
+ invoke = invoke != 0 && desc->invokeContactCreation != 0;
2175
2463
 
2176
2464
  b3ShapeDef def = b3DefaultShapeDef();
2177
2465
  def.density = desc->density;
@@ -2188,6 +2476,10 @@ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 sc
2188
2476
  def.enableHitEvents = body->hitEvents != 0;
2189
2477
  // the caller applies the mass once after instantiating every shape of the description
2190
2478
  def.updateBodyMass = false;
2479
+ def.invokeContactCreation = invoke != 0;
2480
+ // the shape takes the next index on the body, whichever Box3D body it is made on; a body without a key leaves its
2481
+ // shapes at zero, so they fall back to their ids as unkeyed shapes do
2482
+ def.featureKey = body->featureKey == 0 ? 0 : ( body->featureKey << BX_FEATURE_INDEX_BITS ) | (uint64_t)body->shapeCount;
2191
2483
 
2192
2484
  switch ( desc->kind )
2193
2485
  {
@@ -2265,7 +2557,7 @@ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 sc
2265
2557
  body->helperCapacity = capacity;
2266
2558
  }
2267
2559
  b3WorldTransform bodyXf = b3Body_GetTransform( body->id );
2268
- b3Transform world = b3MulTransforms( bodyXf, local );
2560
+ b3WorldTransform world = b3MulWorldTransforms( bodyXf, local );
2269
2561
  b3BodyDef helperDef = b3DefaultBodyDef();
2270
2562
  helperDef.type = b3_staticBody;
2271
2563
  helperDef.position = world.p;
@@ -2291,7 +2583,8 @@ static void bxInstantiate( bxBody* body, int descSlot, b3Transform xf, b3Vec3 sc
2291
2583
  break;
2292
2584
  }
2293
2585
  bxChild child = container->children[i];
2294
- bxInstantiate( body, child.desc, b3MulTransforms( xf, child.transform ), bxMulScale( scale, child.scale ), depth + 1 );
2586
+ bxInstantiate( body, child.desc, b3MulTransforms( xf, child.transform ), bxMulScale( scale, child.scale ), depth + 1,
2587
+ invoke );
2295
2588
  }
2296
2589
  break;
2297
2590
  }
@@ -2308,7 +2601,7 @@ BX_EXPORT void bx_Body_SetShape( int slot, int descSlot )
2308
2601
  body->shapeDesc = descSlot;
2309
2602
  if ( descSlot != 0 )
2310
2603
  {
2311
- bxInstantiate( body, descSlot, b3Transform_identity, b3Vec3_one, 0 );
2604
+ bxInstantiate( body, descSlot, b3Transform_identity, b3Vec3_one, 0, 1 );
2312
2605
  }
2313
2606
  b3Body_ApplyMassFromShapes( body->id );
2314
2607
  }
@@ -3234,8 +3527,27 @@ typedef struct bxRayContext
3234
3527
  int hitSensors;
3235
3528
  int closestOnly;
3236
3529
  int count;
3530
+ // the closest hit so far, for ties
3531
+ uint64_t bestKey;
3532
+ int bestBody;
3533
+ int bestIndex;
3237
3534
  } bxRayContext;
3238
3535
 
3536
+ /// Whether a hit on (key, body, index) wins a tie against the closest so far: the lower feature key, then the lower body
3537
+ /// slot and shape index, so a tie does not go to whichever shape the broad phase's tree happened to give first.
3538
+ static int bxHitPrecedes( uint64_t key, int body, int index, uint64_t bestKey, int bestBody, int bestIndex )
3539
+ {
3540
+ if ( key != bestKey )
3541
+ {
3542
+ return key < bestKey;
3543
+ }
3544
+ if ( body != bestBody )
3545
+ {
3546
+ return body < bestBody;
3547
+ }
3548
+ return index < bestIndex;
3549
+ }
3550
+
3239
3551
  static bxFloatBuffer s_rayHits;
3240
3552
  #define BX_RAY_HIT_STRIDE 11
3241
3553
 
@@ -3244,6 +3556,22 @@ BX_EXPORT float* bx_RayHitsPtr( void )
3244
3556
  return s_rayHits.data;
3245
3557
  }
3246
3558
 
3559
+ // Each hit's point as [x, y, z] doubles, beside its float record
3560
+ static bxDoubleBuffer s_rayHitPoints;
3561
+
3562
+ BX_EXPORT double* bx_RayHitPointsF64Ptr( void )
3563
+ {
3564
+ return s_rayHitPoints.data;
3565
+ }
3566
+
3567
+ static void bxWriteHitPoint( int index, b3Pos point )
3568
+ {
3569
+ bxDoubleBuffer_Reserve( &s_rayHitPoints, ( index + 1 ) * 3 );
3570
+ s_rayHitPoints.data[3 * index + 0] = point.x;
3571
+ s_rayHitPoints.data[3 * index + 1] = point.y;
3572
+ s_rayHitPoints.data[3 * index + 2] = point.z;
3573
+ }
3574
+
3247
3575
  static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, int triangleIndex,
3248
3576
  int childIndex, void* context )
3249
3577
  {
@@ -3259,6 +3587,26 @@ static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float
3259
3587
  {
3260
3588
  return -1.0f;
3261
3589
  }
3590
+ int shapeIndex = bxShapeIndex( s_bodies + bodySlot, shapeId );
3591
+ // The ray is clipped just past the closest hit, not at it, so a shape that ties with it is still reported, and the
3592
+ // tie goes by feature key.
3593
+ float clip = b3MinFloat( nextafterf( fraction, 2.0f ), 1.0f );
3594
+ if ( ctx->closestOnly )
3595
+ {
3596
+ uint64_t key = b3Shape_GetFeatureKey( shapeId );
3597
+ if ( ctx->count > 0 )
3598
+ {
3599
+ float best = s_rayHits.data[6];
3600
+ if ( fraction > best ||
3601
+ ( fraction == best && bxHitPrecedes( key, bodySlot, shapeIndex, ctx->bestKey, ctx->bestBody, ctx->bestIndex ) == 0 ) )
3602
+ {
3603
+ return b3MinFloat( nextafterf( best, 2.0f ), 1.0f );
3604
+ }
3605
+ }
3606
+ ctx->bestKey = key;
3607
+ ctx->bestBody = bodySlot;
3608
+ ctx->bestIndex = shapeIndex;
3609
+ }
3262
3610
  int index = ctx->closestOnly ? 0 : ctx->count;
3263
3611
  bxFloatBuffer_Reserve( &s_rayHits, ( index + 1 ) * BX_RAY_HIT_STRIDE );
3264
3612
  float* out = s_rayHits.data + index * BX_RAY_HIT_STRIDE;
@@ -3272,14 +3620,15 @@ static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float
3272
3620
  out[7] = (float)bodySlot;
3273
3621
  out[8] = (float)bxDescSlotFromShape( shapeId );
3274
3622
  out[9] = (float)triangleIndex;
3275
- out[10] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
3623
+ out[10] = (float)shapeIndex;
3624
+ bxWriteHitPoint( index, point );
3276
3625
  ctx->count = ctx->closestOnly ? 1 : ctx->count + 1;
3277
- return ctx->closestOnly ? fraction : 1.0f;
3626
+ return ctx->closestOnly ? clip : 1.0f;
3278
3627
  }
3279
3628
 
3280
3629
  /// Casts a ray from origin along translation. Returns the number of hits written to bx_RayHitsPtr().
3281
3630
  /// 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,
3631
+ BX_EXPORT int bx_World_CastRay( int w, double ox, double oy, double oz, float dx, float dy, float dz, unsigned int categoryBits,
3283
3632
  unsigned int maskBits, int ignoreBody, int hitSensors, int closestOnly )
3284
3633
  {
3285
3634
  b3WorldId worldId = bxGetWorld( w );
@@ -3287,21 +3636,14 @@ BX_EXPORT int bx_World_CastRay( int w, float ox, float oy, float oz, float dx, f
3287
3636
  {
3288
3637
  return 0;
3289
3638
  }
3290
- bxRayContext ctx = { ignoreBody, hitSensors, closestOnly, 0 };
3639
+ bxRayContext ctx = { ignoreBody, hitSensors, closestOnly, 0, 0, 0, 0 };
3291
3640
  b3QueryFilter filter = b3DefaultQueryFilter();
3292
3641
  filter.categoryBits = (uint64_t)categoryBits;
3293
3642
  filter.maskBits = (uint64_t)maskBits;
3294
- b3World_CastRay( worldId, bxVec3( ox, oy, oz ), bxVec3( dx, dy, dz ), filter, bxRayCallback, &ctx );
3643
+ b3World_CastRay( worldId, bxPos( ox, oy, oz ), bxVec3( dx, dy, dz ), filter, bxRayCallback, &ctx );
3295
3644
  return ctx.count;
3296
3645
  }
3297
3646
 
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
3647
  typedef struct bxShapeCastContext
3306
3648
  {
3307
3649
  int ignoreBody;
@@ -3309,6 +3651,10 @@ typedef struct bxShapeCastContext
3309
3651
  int count;
3310
3652
  float radius;
3311
3653
  b3Vec3 translation;
3654
+ // the closest hit so far, for ties
3655
+ uint64_t bestKey;
3656
+ int bestBody;
3657
+ int bestIndex;
3312
3658
  } bxShapeCastContext;
3313
3659
 
3314
3660
  // 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 +3700,20 @@ static float bxShapeCastCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal,
3354
3700
  touch = b3MaxFloat( touch, 0.0f );
3355
3701
 
3356
3702
  int index = ctx->count;
3703
+ int shapeIndex = bxShapeIndex( s_bodies + bodySlot, shapeId );
3357
3704
  if ( ctx->closestOnly )
3358
3705
  {
3359
- if ( ctx->count > 0 && s_rayHits.data[6] <= touch )
3706
+ // a tie goes by feature key, not to whichever shape the tree gave first
3707
+ uint64_t key = b3Shape_GetFeatureKey( shapeId );
3708
+ if ( ctx->count > 0 &&
3709
+ ( s_rayHits.data[6] < touch ||
3710
+ ( s_rayHits.data[6] == touch && bxHitPrecedes( key, bodySlot, shapeIndex, ctx->bestKey, ctx->bestBody, ctx->bestIndex ) == 0 ) ) )
3360
3711
  {
3361
3712
  return 1.0f;
3362
3713
  }
3714
+ ctx->bestKey = key;
3715
+ ctx->bestBody = bodySlot;
3716
+ ctx->bestIndex = shapeIndex;
3363
3717
  index = 0;
3364
3718
  }
3365
3719
  bxFloatBuffer_Reserve( &s_rayHits, ( index + 1 ) * BX_RAY_HIT_STRIDE );
@@ -3374,7 +3728,8 @@ static float bxShapeCastCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal,
3374
3728
  out[7] = (float)bodySlot;
3375
3729
  out[8] = (float)bxDescSlotFromShape( shapeId );
3376
3730
  out[9] = (float)triangleIndex;
3377
- out[10] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
3731
+ out[10] = (float)shapeIndex;
3732
+ bxWriteHitPoint( index, point );
3378
3733
  ctx->count = ctx->closestOnly ? 1 : ctx->count + 1;
3379
3734
  return 1.0f;
3380
3735
  }
@@ -3391,7 +3746,7 @@ BX_EXPORT int bx_World_CastShape( int w, double ox, double oy, double oz, const
3391
3746
  {
3392
3747
  return 0;
3393
3748
  }
3394
- bxShapeCastContext ctx = { ignoreBody, closestOnly, 0, radius, bxVec3( dx, dy, dz ) };
3749
+ bxShapeCastContext ctx = { ignoreBody, closestOnly, 0, radius, bxVec3( dx, dy, dz ), 0, 0, 0 };
3395
3750
  b3QueryFilter filter = b3DefaultQueryFilter();
3396
3751
  filter.categoryBits = (uint64_t)categoryBits;
3397
3752
  filter.maskBits = (uint64_t)maskBits;