babylon-box3d 0.4.0 → 0.6.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
@@ -739,12 +739,14 @@ BX_EXPORT void bx_World_GetStats( int w )
739
739
  // Events
740
740
  // ---------------------------------------------------------------------------------------------
741
741
 
742
+ static int bxShapeIndex( const bxBody* body, b3ShapeId shapeId );
743
+
742
744
  static bxFloatBuffer s_moveEvents;
743
745
  static bxFloatBuffer s_contactEvents;
744
746
  static bxFloatBuffer s_sensorEvents;
745
747
 
746
748
  #define BX_MOVE_EVENT_STRIDE 9
747
- #define BX_CONTACT_EVENT_STRIDE 12
749
+ #define BX_CONTACT_EVENT_STRIDE 14
748
750
  #define BX_SENSOR_EVENT_STRIDE 5
749
751
 
750
752
  BX_EXPORT float* bx_MoveEventsPtr( void )
@@ -836,9 +838,10 @@ static void bxWriteBeginTouchData( float* out, b3ContactId contactId, b3ShapeId
836
838
  out[6] = impulse;
837
839
  }
838
840
 
839
- /// Each record: [kind (0 begin, 1 end, 2 hit), shapeDescA, shapeDescB, bodyA, bodyB, px, py, pz, nx, ny, nz, value]
840
- /// Begin events carry the contact point, normal and the total normal impulse of the step, hit events the point, normal
841
- /// and approach speed, end events nothing.
841
+ /// Each record: [kind (0 begin, 1 end, 2 hit), shapeDescA, shapeDescB, bodyA, bodyB, px, py, pz, nx, ny, nz, value,
842
+ /// shapeIndexA, shapeIndexB]. Begin events carry the contact point, normal and the total normal impulse of the step, hit
843
+ /// events the point, normal and approach speed, end events nothing. The shape indices are each shape's place among its
844
+ /// body's shapes, which it keeps for the body's lifetime (-1 if not found).
842
845
  BX_EXPORT int bx_World_GetContactEvents( int w )
843
846
  {
844
847
  b3WorldId worldId = bxGetWorld( w );
@@ -867,6 +870,8 @@ BX_EXPORT int bx_World_GetContactEvents( int w )
867
870
  out[2] = (float)bxDescSlotFromShape( e->shapeIdB );
868
871
  out[3] = (float)bodyA;
869
872
  out[4] = (float)bodyB;
873
+ out[12] = (float)bxShapeIndex( s_bodies + bodyA, e->shapeIdA );
874
+ out[13] = (float)bxShapeIndex( s_bodies + bodyB, e->shapeIdB );
870
875
  bxWriteBeginTouchData( out + 5, e->contactId, e->shapeIdA );
871
876
  out += BX_CONTACT_EVENT_STRIDE;
872
877
  count += 1;
@@ -886,6 +891,8 @@ BX_EXPORT int bx_World_GetContactEvents( int w )
886
891
  out[2] = (float)bxDescSlotFromShape( e->shapeIdB );
887
892
  out[3] = (float)bodyA;
888
893
  out[4] = (float)bodyB;
894
+ out[12] = (float)bxShapeIndex( s_bodies + bodyA, e->shapeIdA );
895
+ out[13] = (float)bxShapeIndex( s_bodies + bodyB, e->shapeIdB );
889
896
  out[5] = e->point.x;
890
897
  out[6] = e->point.y;
891
898
  out[7] = e->point.z;
@@ -911,6 +918,8 @@ BX_EXPORT int bx_World_GetContactEvents( int w )
911
918
  out[2] = (float)bxDescSlotFromShape( e->shapeIdB );
912
919
  out[3] = (float)bodyA;
913
920
  out[4] = (float)bodyB;
921
+ out[12] = (float)bxShapeIndex( s_bodies + bodyA, e->shapeIdA );
922
+ out[13] = (float)bxShapeIndex( s_bodies + bodyB, e->shapeIdB );
914
923
  memset( out + 5, 0, 7 * sizeof( float ) );
915
924
  out += BX_CONTACT_EVENT_STRIDE;
916
925
  count += 1;
@@ -1237,6 +1246,189 @@ BX_EXPORT void bx_Body_SetBullet( int slot, int flag )
1237
1246
  b3Body_SetBullet( body->id, flag != 0 );
1238
1247
  }
1239
1248
 
1249
+ // Continuous collision for this body alone (wasm/patches/0002). The world's switch still applies.
1250
+ BX_EXPORT void bx_Body_EnableContinuous( int slot, int flag )
1251
+ {
1252
+ BX_BODY( slot );
1253
+ b3Body_EnableContinuous( body->id, flag != 0 );
1254
+ }
1255
+
1256
+ BX_EXPORT int bx_Body_IsContinuousEnabled( int slot )
1257
+ {
1258
+ BX_BODY_RET( slot, 0 );
1259
+ return b3Body_IsContinuousEnabled( body->id ) ? 1 : 0;
1260
+ }
1261
+
1262
+ // Crushable contacts for the body's shape i (wasm/patches/0003): each of its contacts pushes with at most maxForce
1263
+ // newtons, and a plastic one keeps its overlap instead of pushing out. A maxForce of zero or less makes it rigid.
1264
+ BX_EXPORT void bx_Body_SetShapeCrush( int slot, int index, float maxForce, int plastic )
1265
+ {
1266
+ BX_BODY( slot );
1267
+ if ( index < 0 || index >= body->shapeCount || b3Shape_IsValid( body->shapes[index] ) == false )
1268
+ {
1269
+ return;
1270
+ }
1271
+ b3Shape_SetCrushLimit( body->shapes[index], maxForce, plastic != 0 );
1272
+ }
1273
+
1274
+ /// scratch: [maxForce, plastic] for the body's shape i, zeros when it is rigid or there is no such shape.
1275
+ BX_EXPORT void bx_Body_GetShapeCrush( int slot, int index )
1276
+ {
1277
+ s_scratch[0] = 0.0f;
1278
+ s_scratch[1] = 0.0f;
1279
+ BX_BODY( slot );
1280
+ if ( index < 0 || index >= body->shapeCount || b3Shape_IsValid( body->shapes[index] ) == false )
1281
+ {
1282
+ return;
1283
+ }
1284
+ s_scratch[0] = b3Shape_GetCrushLimit( body->shapes[index] );
1285
+ s_scratch[1] = b3Shape_IsCrushPlastic( body->shapes[index] ) ? 1.0f : 0.0f;
1286
+ }
1287
+
1288
+ // ---------------------------------------------------------------------------------------------
1289
+ // Contact readback
1290
+ // ---------------------------------------------------------------------------------------------
1291
+
1292
+ static bxFloatBuffer s_bodyContacts;
1293
+ static b3ContactData* s_contactData;
1294
+ static int s_contactDataCapacity;
1295
+
1296
+ #define BX_BODY_CONTACT_STRIDE 48
1297
+ #define BX_BODY_CONTACT_POINT_STRIDE 9
1298
+
1299
+ BX_EXPORT float* bx_BodyContactsPtr( void )
1300
+ {
1301
+ return s_bodyContacts.data;
1302
+ }
1303
+
1304
+ /// The index of shapeId among the body's shapes, or -1.
1305
+ static int bxShapeIndex( const bxBody* body, b3ShapeId shapeId )
1306
+ {
1307
+ for ( int i = 0; i < body->shapeCount; ++i )
1308
+ {
1309
+ if ( B3_ID_EQUALS( body->shapes[i], shapeId ) )
1310
+ {
1311
+ return i;
1312
+ }
1313
+ }
1314
+ return -1;
1315
+ }
1316
+
1317
+ /// The material index a contact point meets on a mesh or height field shape: the triangle's for a mesh, its cell's
1318
+ /// for a height field, as the description was given them. 0 for anything else, or a shape without materials.
1319
+ static int bxContactMaterial( const bxBody* body, int shapeIndex, int triangleIndex )
1320
+ {
1321
+ if ( body == NULL || shapeIndex < 0 || triangleIndex < 0 )
1322
+ {
1323
+ return 0;
1324
+ }
1325
+ int geomSlot = body->geomDescs[shapeIndex];
1326
+ if ( geomSlot <= 0 || geomSlot >= s_descCount || s_descs[geomSlot].alive == 0 )
1327
+ {
1328
+ return 0;
1329
+ }
1330
+ const bxShapeDesc* geom = s_descs + geomSlot;
1331
+ if ( geom->kind == bx_heightFieldDesc && geom->heightField != NULL )
1332
+ {
1333
+ const uint8_t* cells = b3GetHeightFieldMaterialIndices( geom->heightField );
1334
+ return cells != NULL ? cells[triangleIndex >> 1] : 0;
1335
+ }
1336
+ if ( geom->kind == bx_meshDesc && geom->mesh != NULL )
1337
+ {
1338
+ const uint8_t* triangles = b3GetMeshMaterialIndices( geom->mesh );
1339
+ return triangles != NULL ? triangles[triangleIndex] : 0;
1340
+ }
1341
+ return 0;
1342
+ }
1343
+
1344
+ /// Every touching contact manifold on the body, as the body feels it, written from bx_BodyContactsPtr. Speculative
1345
+ /// points are included; a point that carried no impulse this step has a total normal impulse of zero.
1346
+ /// Each record, 48 floats:
1347
+ /// [0] this body's shape index, [1] the other body's slot (0 if none), [2] the other body's shape index (-1 if
1348
+ /// unknown), [3] the other shape's description slot, [4..6] the unit normal along which the other shape pushes this
1349
+ /// body, [7..9] the friction impulse on this body and [10] the twist impulse about the normal, both of the last
1350
+ /// substep, [11] the point count (1 to 4),
1351
+ /// then 4 points of 9: [x, y, z] in world space, separation (negative if penetrating), normal impulse of the last
1352
+ /// substep (so the force is that over the substep), Box3D's total normal impulse (summed over every solver pass
1353
+ /// of every substep, so not a physical impulse: nonzero means the point carried load this step), relative normal
1354
+ /// velocity before the solve (negative when approaching), the triangle index on a mesh or height field (-1 if
1355
+ /// none), and the material index there.
1356
+ BX_EXPORT int bx_Body_GetContacts( int slot )
1357
+ {
1358
+ s_bodyContacts.count = 0;
1359
+ BX_BODY_RET( slot, 0 );
1360
+
1361
+ int capacity = b3Body_GetContactCapacity( body->id );
1362
+ if ( capacity > s_contactDataCapacity )
1363
+ {
1364
+ s_contactDataCapacity = capacity;
1365
+ s_contactData = (b3ContactData*)realloc( s_contactData, (size_t)capacity * sizeof( b3ContactData ) );
1366
+ }
1367
+ int contactCount = capacity > 0 ? b3Body_GetContactData( body->id, s_contactData, capacity ) : 0;
1368
+
1369
+ int manifoldTotal = 0;
1370
+ for ( int i = 0; i < contactCount; ++i )
1371
+ {
1372
+ manifoldTotal += s_contactData[i].manifoldCount;
1373
+ }
1374
+ bxFloatBuffer_Reserve( &s_bodyContacts, manifoldTotal * BX_BODY_CONTACT_STRIDE );
1375
+
1376
+ b3Pos center = b3Body_GetWorldCenter( body->id );
1377
+ float* out = s_bodyContacts.data;
1378
+ int count = 0;
1379
+ for ( int i = 0; i < contactCount; ++i )
1380
+ {
1381
+ const b3ContactData* data = s_contactData + i;
1382
+ int selfIsA = B3_ID_EQUALS( b3Shape_GetBody( data->shapeIdA ), body->id );
1383
+ b3ShapeId selfShape = selfIsA ? data->shapeIdA : data->shapeIdB;
1384
+ b3ShapeId otherShape = selfIsA ? data->shapeIdB : data->shapeIdA;
1385
+ int otherSlot = bxBodySlotFromShape( otherShape );
1386
+ const bxBody* other =
1387
+ otherSlot > 0 && otherSlot < s_bodyCount && s_bodies[otherSlot].alive ? s_bodies + otherSlot : NULL;
1388
+ int selfIndex = bxShapeIndex( body, selfShape );
1389
+ int otherIndex = other != NULL ? bxShapeIndex( other, otherShape ) : -1;
1390
+ // Box3D's normal points from shape A to shape B, and the friction and twist impulses act positively on B.
1391
+ float sign = selfIsA ? -1.0f : 1.0f;
1392
+
1393
+ for ( int m = 0; m < data->manifoldCount; ++m )
1394
+ {
1395
+ const b3Manifold* manifold = data->manifolds + m;
1396
+ memset( out, 0, BX_BODY_CONTACT_STRIDE * sizeof( float ) );
1397
+ out[0] = (float)selfIndex;
1398
+ out[1] = (float)( other != NULL ? otherSlot : 0 );
1399
+ out[2] = (float)otherIndex;
1400
+ out[3] = (float)bxDescSlotFromShape( otherShape );
1401
+ out[4] = sign * manifold->normal.x;
1402
+ out[5] = sign * manifold->normal.y;
1403
+ out[6] = sign * manifold->normal.z;
1404
+ out[7] = sign * manifold->frictionImpulse.x;
1405
+ out[8] = sign * manifold->frictionImpulse.y;
1406
+ out[9] = sign * manifold->frictionImpulse.z;
1407
+ out[10] = manifold->twistImpulse;
1408
+ out[11] = (float)manifold->pointCount;
1409
+ for ( int p = 0; p < manifold->pointCount; ++p )
1410
+ {
1411
+ const b3ManifoldPoint* mp = manifold->points + p;
1412
+ float* po = out + 12 + p * BX_BODY_CONTACT_POINT_STRIDE;
1413
+ b3Pos point = b3OffsetPos( center, selfIsA ? mp->anchorA : mp->anchorB );
1414
+ po[0] = (float)point.x;
1415
+ po[1] = (float)point.y;
1416
+ po[2] = (float)point.z;
1417
+ po[3] = mp->separation;
1418
+ po[4] = mp->normalImpulse;
1419
+ po[5] = mp->totalNormalImpulse;
1420
+ po[6] = mp->normalVelocity;
1421
+ po[7] = (float)mp->triangleIndex;
1422
+ po[8] = (float)bxContactMaterial( other, otherIndex, mp->triangleIndex );
1423
+ }
1424
+ out += BX_BODY_CONTACT_STRIDE;
1425
+ count += 1;
1426
+ }
1427
+ }
1428
+ s_bodyContacts.count = count;
1429
+ return count;
1430
+ }
1431
+
1240
1432
  BX_EXPORT void bx_Body_AllowFastRotation( int slot, int flag )
1241
1433
  {
1242
1434
  BX_BODY( slot );
@@ -1477,17 +1669,23 @@ BX_EXPORT int bx_ShapeDesc_CreateCylinder( float height, float radius, int sides
1477
1669
  return slot;
1478
1670
  }
1479
1671
 
1480
- /// points: pointCount * 3 floats
1481
- BX_EXPORT int bx_ShapeDesc_CreateHull( const float* points, int pointCount )
1672
+ /// A hull of the points, or NULL. Box3D hulls are limited to 128 vertices, faces and edges. Dense meshes (a torus knot,
1673
+ /// a sphere) blow the edge budget long before the vertex budget, so simplify progressively until the builder accepts it.
1674
+ static b3HullData* bxCreateHull( const float* points, int pointCount )
1482
1675
  {
1483
- // Box3D hulls are limited to 128 vertices, faces and edges. Dense meshes (a torus knot, a sphere) blow the
1484
- // edge budget long before the vertex budget, so simplify progressively until the builder accepts it.
1485
1676
  static const int s_vertexBudgets[] = { 40, 28, 20, 14, 10, 8 };
1486
1677
  b3HullData* hull = NULL;
1487
1678
  for ( int i = 0; i < (int)( sizeof( s_vertexBudgets ) / sizeof( s_vertexBudgets[0] ) ) && hull == NULL; ++i )
1488
1679
  {
1489
1680
  hull = b3CreateHull( (const b3Vec3*)points, pointCount, s_vertexBudgets[i] );
1490
1681
  }
1682
+ return hull;
1683
+ }
1684
+
1685
+ /// points: pointCount * 3 floats
1686
+ BX_EXPORT int bx_ShapeDesc_CreateHull( const float* points, int pointCount )
1687
+ {
1688
+ b3HullData* hull = bxCreateHull( points, pointCount );
1491
1689
  if ( hull == NULL )
1492
1690
  {
1493
1691
  return 0;
@@ -2115,6 +2313,178 @@ BX_EXPORT void bx_Body_SetShape( int slot, int descSlot )
2115
2313
  b3Body_ApplyMassFromShapes( body->id );
2116
2314
  }
2117
2315
 
2316
+ // ---------------------------------------------------------------------------------------------
2317
+ // Per-shape handles: change one of a body's shapes without rebuilding the others. Changing a shape resets only its own
2318
+ // contacts, where bx_Body_SetShape drops every contact the body has. A shape keeps its index for the body's lifetime,
2319
+ // removed or not, so indices in contacts, events and ray hits stay valid. None of these changes the body's mass.
2320
+ // ---------------------------------------------------------------------------------------------
2321
+
2322
+ static b3ShapeId bxBodyShape( int slot, int index )
2323
+ {
2324
+ bxBody* body = bxGetBody( slot );
2325
+ if ( body == NULL || index < 0 || index >= body->shapeCount || b3Shape_IsValid( body->shapes[index] ) == false )
2326
+ {
2327
+ return b3_nullShapeId;
2328
+ }
2329
+ return body->shapes[index];
2330
+ }
2331
+
2332
+ /// Replaces shape `index` with the hull of the points, given in the body's frame. Returns 1, or 0 when there is no such
2333
+ /// shape or the points make no hull.
2334
+ BX_EXPORT int bx_Body_SetShapeHull( int slot, int index, const float* points, int pointCount )
2335
+ {
2336
+ b3ShapeId shapeId = bxBodyShape( slot, index );
2337
+ if ( B3_IS_NULL( shapeId ) )
2338
+ {
2339
+ return 0;
2340
+ }
2341
+ b3HullData* hull = bxCreateHull( points, pointCount );
2342
+ if ( hull == NULL )
2343
+ {
2344
+ return 0;
2345
+ }
2346
+ // the world keeps its own copy
2347
+ b3Shape_SetHull( shapeId, hull );
2348
+ b3DestroyHull( hull );
2349
+ return 1;
2350
+ }
2351
+
2352
+ /// Moves shape `index` by (dx, dy, dz) in the body's frame. Works on hulls, spheres and capsules.
2353
+ BX_EXPORT int bx_Body_TranslateShape( int slot, int index, float dx, float dy, float dz )
2354
+ {
2355
+ b3ShapeId shapeId = bxBodyShape( slot, index );
2356
+ if ( B3_IS_NULL( shapeId ) )
2357
+ {
2358
+ return 0;
2359
+ }
2360
+ b3Vec3 offset = bxVec3( dx, dy, dz );
2361
+ switch ( b3Shape_GetType( shapeId ) )
2362
+ {
2363
+ case b3_hullShape:
2364
+ {
2365
+ b3Transform xf = { offset, b3Quat_identity };
2366
+ b3HullData* moved = b3CloneAndTransformHull( b3Shape_GetHull( shapeId ), xf, b3Vec3_one );
2367
+ if ( moved == NULL )
2368
+ {
2369
+ return 0;
2370
+ }
2371
+ b3Shape_SetHull( shapeId, moved );
2372
+ b3DestroyHull( moved );
2373
+ return 1;
2374
+ }
2375
+ case b3_sphereShape:
2376
+ {
2377
+ b3Sphere sphere = b3Shape_GetSphere( shapeId );
2378
+ sphere.center = b3Add( sphere.center, offset );
2379
+ b3Shape_SetSphere( shapeId, &sphere );
2380
+ return 1;
2381
+ }
2382
+ case b3_capsuleShape:
2383
+ {
2384
+ b3Capsule capsule = b3Shape_GetCapsule( shapeId );
2385
+ capsule.center1 = b3Add( capsule.center1, offset );
2386
+ capsule.center2 = b3Add( capsule.center2, offset );
2387
+ b3Shape_SetCapsule( shapeId, &capsule );
2388
+ return 1;
2389
+ }
2390
+ default:
2391
+ return 0;
2392
+ }
2393
+ }
2394
+
2395
+ /// Sets shape `index`'s collision filter. Its contacts are re-found on the next step.
2396
+ BX_EXPORT void bx_Body_SetShapeFilter( int slot, int index, unsigned int categoryBits, unsigned int maskBits, int groupIndex )
2397
+ {
2398
+ b3ShapeId shapeId = bxBodyShape( slot, index );
2399
+ if ( B3_IS_NULL( shapeId ) )
2400
+ {
2401
+ return;
2402
+ }
2403
+ b3Filter filter = b3Shape_GetFilter( shapeId );
2404
+ filter.categoryBits = (uint64_t)categoryBits;
2405
+ filter.maskBits = (uint64_t)maskBits;
2406
+ filter.groupIndex = groupIndex;
2407
+ b3Shape_SetFilter( shapeId, filter, true );
2408
+ }
2409
+
2410
+ /// Removes shape `index` from the body. The other shapes keep their indices, and this one stays empty.
2411
+ BX_EXPORT void bx_Body_RemoveShape( int slot, int index )
2412
+ {
2413
+ b3ShapeId shapeId = bxBodyShape( slot, index );
2414
+ if ( B3_IS_NULL( shapeId ) )
2415
+ {
2416
+ return;
2417
+ }
2418
+ bxBody* body = bxGetBody( slot );
2419
+ b3DestroyShape( shapeId, false );
2420
+ int geomSlot = body->geomDescs[index];
2421
+ if ( geomSlot != body->shapeDescs[index] )
2422
+ {
2423
+ // a mesh baked for this body alone, nothing else refers to it
2424
+ bx_ShapeDesc_Destroy( geomSlot );
2425
+ }
2426
+ bxDescRelease( geomSlot );
2427
+ body->shapes[index] = b3_nullShapeId;
2428
+ body->shapeDescs[index] = 0;
2429
+ body->geomDescs[index] = 0;
2430
+ }
2431
+
2432
+ /// Is there a shape at `index`? 0 once it has been removed.
2433
+ BX_EXPORT int bx_Body_HasShape( int slot, int index )
2434
+ {
2435
+ return B3_IS_NON_NULL( bxBodyShape( slot, index ) ) ? 1 : 0;
2436
+ }
2437
+
2438
+ /// scratch: [kind (0 sphere, 1 capsule, 2 hull, else -1), vertexCount, aabb min xyz, aabb max xyz] of shape `index`,
2439
+ /// its bounds in the body's frame, so a test or the sim can see where a changed shape now is.
2440
+ BX_EXPORT void bx_Body_GetShapeInfo( int slot, int index )
2441
+ {
2442
+ memset( s_scratch, 0, 8 * sizeof( float ) );
2443
+ s_scratch[0] = -1.0f;
2444
+ b3ShapeId shapeId = bxBodyShape( slot, index );
2445
+ if ( B3_IS_NULL( shapeId ) )
2446
+ {
2447
+ return;
2448
+ }
2449
+ b3AABB box = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } };
2450
+ switch ( b3Shape_GetType( shapeId ) )
2451
+ {
2452
+ case b3_sphereShape:
2453
+ {
2454
+ b3Sphere s = b3Shape_GetSphere( shapeId );
2455
+ b3Vec3 r = { s.radius, s.radius, s.radius };
2456
+ box = (b3AABB){ b3Sub( s.center, r ), b3Add( s.center, r ) };
2457
+ s_scratch[0] = 0.0f;
2458
+ break;
2459
+ }
2460
+ case b3_capsuleShape:
2461
+ {
2462
+ b3Capsule c = b3Shape_GetCapsule( shapeId );
2463
+ b3Vec3 r = { c.radius, c.radius, c.radius };
2464
+ box = (b3AABB){ b3Sub( b3Min( c.center1, c.center2 ), r ), b3Add( b3Max( c.center1, c.center2 ), r ) };
2465
+ s_scratch[0] = 1.0f;
2466
+ break;
2467
+ }
2468
+ case b3_hullShape:
2469
+ {
2470
+ const b3HullData* hull = b3Shape_GetHull( shapeId );
2471
+ const b3Vec3* points = b3GetHullPoints( hull );
2472
+ for ( int i = 0; i < hull->vertexCount; ++i )
2473
+ {
2474
+ box.lowerBound = b3Min( box.lowerBound, points[i] );
2475
+ box.upperBound = b3Max( box.upperBound, points[i] );
2476
+ }
2477
+ s_scratch[0] = 2.0f;
2478
+ s_scratch[1] = (float)hull->vertexCount;
2479
+ break;
2480
+ }
2481
+ default:
2482
+ return;
2483
+ }
2484
+ bxWriteVec3( 2, box.lowerBound );
2485
+ bxWriteVec3( 5, box.upperBound );
2486
+ }
2487
+
2118
2488
  /// Properties that can be changed on live shapes, see bx_Body_SyncShapeDesc.
2119
2489
  typedef enum bxSyncFlags
2120
2490
  {
@@ -2902,13 +3272,13 @@ static float bxRayCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float
2902
3272
  out[7] = (float)bodySlot;
2903
3273
  out[8] = (float)bxDescSlotFromShape( shapeId );
2904
3274
  out[9] = (float)triangleIndex;
2905
- out[10] = 0.0f;
3275
+ out[10] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
2906
3276
  ctx->count = ctx->closestOnly ? 1 : ctx->count + 1;
2907
3277
  return ctx->closestOnly ? fraction : 1.0f;
2908
3278
  }
2909
3279
 
2910
3280
  /// Casts a ray from origin along translation. Returns the number of hits written to bx_RayHitsPtr().
2911
- /// Each hit: [px, py, pz, nx, ny, nz, fraction, bodySlot, shapeDesc, triangleIndex, reserved]
3281
+ /// Each hit: [px, py, pz, nx, ny, nz, fraction, bodySlot, shapeDesc, triangleIndex, shapeIndex]
2912
3282
  BX_EXPORT int bx_World_CastRay( int w, float ox, float oy, float oz, float dx, float dy, float dz, unsigned int categoryBits,
2913
3283
  unsigned int maskBits, int ignoreBody, int hitSensors, int closestOnly )
2914
3284
  {
@@ -2924,3 +3294,224 @@ BX_EXPORT int bx_World_CastRay( int w, float ox, float oy, float oz, float dx, f
2924
3294
  b3World_CastRay( worldId, bxVec3( ox, oy, oz ), bxVec3( dx, dy, dz ), filter, bxRayCallback, &ctx );
2925
3295
  return ctx.count;
2926
3296
  }
3297
+
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
+ typedef struct bxShapeCastContext
3306
+ {
3307
+ int ignoreBody;
3308
+ int closestOnly;
3309
+ int count;
3310
+ float radius;
3311
+ b3Vec3 translation;
3312
+ } bxShapeCastContext;
3313
+
3314
+ // Box3D's shape cast stops where the two cores are max(slop, radii - slop) apart, so that a body moved to the hit is
3315
+ // left just short of touching: 5 mm short with no radius, 5 mm too deep once the radii pass 1 cm. A query wants the
3316
+ // touch itself, so each fraction is moved on by the gap left, at the rate the cast closes it along the hit normal.
3317
+ // Box3D is never told to clip, since it would clip on its own fractions: every hit comes back, and a closest-only cast
3318
+ // keeps the one that touches first.
3319
+ static float bxShapeCastCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId,
3320
+ int triangleIndex, int childIndex, void* context )
3321
+ {
3322
+ (void)userMaterialId;
3323
+ (void)childIndex;
3324
+ bxShapeCastContext* ctx = (bxShapeCastContext*)context;
3325
+ if ( b3Shape_IsSensor( shapeId ) )
3326
+ {
3327
+ return -1.0f;
3328
+ }
3329
+ int bodySlot = bxBodySlotFromShape( shapeId );
3330
+ if ( bodySlot == 0 || bodySlot == ctx->ignoreBody )
3331
+ {
3332
+ return -1.0f;
3333
+ }
3334
+
3335
+ float otherRadius = 0.0f;
3336
+ b3ShapeType type = b3Shape_GetType( shapeId );
3337
+ if ( type == b3_sphereShape )
3338
+ {
3339
+ otherRadius = b3Shape_GetSphere( shapeId ).radius;
3340
+ }
3341
+ else if ( type == b3_capsuleShape )
3342
+ {
3343
+ otherRadius = b3Shape_GetCapsule( shapeId ).radius;
3344
+ }
3345
+ float totalRadius = ctx->radius + otherRadius;
3346
+ float target = b3MaxFloat( B3_LINEAR_SLOP, totalRadius - B3_LINEAR_SLOP );
3347
+ float closing = -b3Dot( ctx->translation, normal );
3348
+ float touch = closing > FLT_EPSILON ? fraction + ( target - totalRadius ) / closing : fraction;
3349
+ if ( touch > 1.0f )
3350
+ {
3351
+ // the cast ends before the surfaces meet
3352
+ return 1.0f;
3353
+ }
3354
+ touch = b3MaxFloat( touch, 0.0f );
3355
+
3356
+ int index = ctx->count;
3357
+ if ( ctx->closestOnly )
3358
+ {
3359
+ if ( ctx->count > 0 && s_rayHits.data[6] <= touch )
3360
+ {
3361
+ return 1.0f;
3362
+ }
3363
+ index = 0;
3364
+ }
3365
+ bxFloatBuffer_Reserve( &s_rayHits, ( index + 1 ) * BX_RAY_HIT_STRIDE );
3366
+ float* out = s_rayHits.data + index * BX_RAY_HIT_STRIDE;
3367
+ out[0] = (float)point.x;
3368
+ out[1] = (float)point.y;
3369
+ out[2] = (float)point.z;
3370
+ out[3] = normal.x;
3371
+ out[4] = normal.y;
3372
+ out[5] = normal.z;
3373
+ out[6] = touch;
3374
+ out[7] = (float)bodySlot;
3375
+ out[8] = (float)bxDescSlotFromShape( shapeId );
3376
+ out[9] = (float)triangleIndex;
3377
+ out[10] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
3378
+ ctx->count = ctx->closestOnly ? 1 : ctx->count + 1;
3379
+ return 1.0f;
3380
+ }
3381
+
3382
+ /// Casts a convex shape, the hull of `count` points (xyz floats, relative to the origin) grown by `radius`, from the
3383
+ /// origin along the translation. The origin is given in doubles so a cast stays precise far from the world origin in a
3384
+ /// double precision build. Hits are written to bx_RayHitsPtr() as bx_World_CastRay writes them: the fraction is how far
3385
+ /// along the translation the shape goes before its surface touches, and the point is where. Returns the hit count.
3386
+ BX_EXPORT int bx_World_CastShape( int w, double ox, double oy, double oz, const float* points, int count, float radius, float dx,
3387
+ float dy, float dz, unsigned int categoryBits, unsigned int maskBits, int ignoreBody, int closestOnly )
3388
+ {
3389
+ b3WorldId worldId = bxGetWorld( w );
3390
+ if ( B3_IS_NULL( worldId ) || count <= 0 || count > B3_MAX_SHAPE_CAST_POINTS )
3391
+ {
3392
+ return 0;
3393
+ }
3394
+ bxShapeCastContext ctx = { ignoreBody, closestOnly, 0, radius, bxVec3( dx, dy, dz ) };
3395
+ b3QueryFilter filter = b3DefaultQueryFilter();
3396
+ filter.categoryBits = (uint64_t)categoryBits;
3397
+ filter.maskBits = (uint64_t)maskBits;
3398
+ b3ShapeProxy proxy = { (const b3Vec3*)points, count, radius };
3399
+ b3World_CastShape( worldId, bxPos( ox, oy, oz ), &proxy, bxVec3( dx, dy, dz ), filter, bxShapeCastCallback, &ctx );
3400
+ return ctx.count;
3401
+ }
3402
+
3403
+ static bxFloatBuffer s_overlaps;
3404
+ #define BX_OVERLAP_STRIDE 3
3405
+
3406
+ BX_EXPORT float* bx_OverlapsPtr( void )
3407
+ {
3408
+ return s_overlaps.data;
3409
+ }
3410
+
3411
+ typedef struct bxOverlapContext
3412
+ {
3413
+ int ignoreBody;
3414
+ int count;
3415
+ } bxOverlapContext;
3416
+
3417
+ static bool bxOverlapCallback( b3ShapeId shapeId, void* context )
3418
+ {
3419
+ bxOverlapContext* ctx = (bxOverlapContext*)context;
3420
+ if ( b3Shape_IsSensor( shapeId ) )
3421
+ {
3422
+ return true;
3423
+ }
3424
+ int bodySlot = bxBodySlotFromShape( shapeId );
3425
+ if ( bodySlot == 0 || bodySlot == ctx->ignoreBody )
3426
+ {
3427
+ return true;
3428
+ }
3429
+ bxFloatBuffer_Reserve( &s_overlaps, ( ctx->count + 1 ) * BX_OVERLAP_STRIDE );
3430
+ float* out = s_overlaps.data + ctx->count * BX_OVERLAP_STRIDE;
3431
+ out[0] = (float)bodySlot;
3432
+ out[1] = (float)bxShapeIndex( s_bodies + bodySlot, shapeId );
3433
+ out[2] = (float)bxDescSlotFromShape( shapeId );
3434
+ ctx->count += 1;
3435
+ return true;
3436
+ }
3437
+
3438
+ /// Every shape the convex shape (the hull of `count` points relative to the origin, grown by `radius`) overlaps,
3439
+ /// written to bx_OverlapsPtr() as [bodySlot, shapeIndex, shapeDesc]. Returns the count.
3440
+ BX_EXPORT int bx_World_OverlapShape( int w, double ox, double oy, double oz, const float* points, int count, float radius,
3441
+ unsigned int categoryBits, unsigned int maskBits, int ignoreBody )
3442
+ {
3443
+ b3WorldId worldId = bxGetWorld( w );
3444
+ if ( B3_IS_NULL( worldId ) || count <= 0 || count > B3_MAX_SHAPE_CAST_POINTS )
3445
+ {
3446
+ return 0;
3447
+ }
3448
+ bxOverlapContext ctx = { ignoreBody, 0 };
3449
+ b3QueryFilter filter = b3DefaultQueryFilter();
3450
+ filter.categoryBits = (uint64_t)categoryBits;
3451
+ filter.maskBits = (uint64_t)maskBits;
3452
+ b3ShapeProxy proxy = { (const b3Vec3*)points, count, radius };
3453
+ b3World_OverlapShape( worldId, bxPos( ox, oy, oz ), &proxy, filter, bxOverlapCallback, &ctx );
3454
+ return ctx.count;
3455
+ }
3456
+
3457
+ static bxFloatBuffer s_geometry;
3458
+
3459
+ BX_EXPORT float* bx_GeometryPtr( void )
3460
+ {
3461
+ return s_geometry.data;
3462
+ }
3463
+
3464
+ /// The geometry of the body's shape `index`, in the body's frame, written to bx_GeometryPtr(): a sphere as
3465
+ /// [cx, cy, cz, radius], a capsule as [x1, y1, z1, x2, y2, z2, radius], a hull as its points, three floats each. Returns
3466
+ /// the float count, 0 when there is no such shape or it is of another kind (bx_Body_GetShapeInfo says which).
3467
+ BX_EXPORT int bx_Body_GetShapeGeometry( int slot, int index )
3468
+ {
3469
+ s_geometry.count = 0;
3470
+ b3ShapeId shapeId = bxBodyShape( slot, index );
3471
+ if ( B3_IS_NULL( shapeId ) )
3472
+ {
3473
+ return 0;
3474
+ }
3475
+ switch ( b3Shape_GetType( shapeId ) )
3476
+ {
3477
+ case b3_sphereShape:
3478
+ {
3479
+ b3Sphere s = b3Shape_GetSphere( shapeId );
3480
+ bxFloatBuffer_Reserve( &s_geometry, 4 );
3481
+ float* out = s_geometry.data;
3482
+ out[0] = s.center.x;
3483
+ out[1] = s.center.y;
3484
+ out[2] = s.center.z;
3485
+ out[3] = s.radius;
3486
+ s_geometry.count = 4;
3487
+ break;
3488
+ }
3489
+ case b3_capsuleShape:
3490
+ {
3491
+ b3Capsule c = b3Shape_GetCapsule( shapeId );
3492
+ bxFloatBuffer_Reserve( &s_geometry, 7 );
3493
+ float* out = s_geometry.data;
3494
+ out[0] = c.center1.x;
3495
+ out[1] = c.center1.y;
3496
+ out[2] = c.center1.z;
3497
+ out[3] = c.center2.x;
3498
+ out[4] = c.center2.y;
3499
+ out[5] = c.center2.z;
3500
+ out[6] = c.radius;
3501
+ s_geometry.count = 7;
3502
+ break;
3503
+ }
3504
+ case b3_hullShape:
3505
+ {
3506
+ const b3HullData* hull = b3Shape_GetHull( shapeId );
3507
+ const b3Vec3* points = b3GetHullPoints( hull );
3508
+ bxFloatBuffer_Reserve( &s_geometry, 3 * hull->vertexCount );
3509
+ memcpy( s_geometry.data, points, (size_t)hull->vertexCount * sizeof( b3Vec3 ) );
3510
+ s_geometry.count = 3 * hull->vertexCount;
3511
+ break;
3512
+ }
3513
+ default:
3514
+ break;
3515
+ }
3516
+ return s_geometry.count;
3517
+ }