@react-three/rapier 0.6.7 → 0.6.9

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.
@@ -5,6 +5,7 @@ import { useAsset } from 'use-asset';
5
5
  import { useFrame } from '@react-three/fiber';
6
6
  import { Quaternion, Euler, Vector3, Object3D, Matrix4, InstancedMesh, MeshBasicMaterial, Color, PlaneGeometry, ConeBufferGeometry, CapsuleBufferGeometry, CylinderBufferGeometry, BufferGeometry, BufferAttribute, SphereBufferGeometry, BoxBufferGeometry, DynamicDrawUsage } from 'three';
7
7
  import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils';
8
+ import { clamp } from 'three/src/math/MathUtils';
8
9
  import { RoundedBoxGeometry } from 'three-stdlib';
9
10
 
10
11
  const _quaternion = new Quaternion();
@@ -24,7 +25,7 @@ const rapierVector3ToVector3 = ({
24
25
  x,
25
26
  y,
26
27
  z
27
- }) => _vector3.set(x, y, z).clone();
28
+ }) => _vector3.set(x, y, z);
28
29
  const rapierQuaternionToQuaternion = ({
29
30
  x,
30
31
  y,
@@ -70,6 +71,12 @@ const scaleColliderArgs = (shape, args, scale) => {
70
71
  const scaleArray = [scale.x, scale.y, scale.z, scale.x, scale.x];
71
72
  return newArgs.map((arg, index) => scaleArray[index] * arg);
72
73
  };
74
+
75
+ const applyColliderOptions = (collider, options) => {
76
+ if (options.collisionGroups !== undefined) collider.setCollisionGroups(options.collisionGroups);
77
+ if (options.solverGroups !== undefined) collider.setSolverGroups(options.solverGroups);
78
+ };
79
+
73
80
  const createColliderFromOptions = ({
74
81
  options,
75
82
  world,
@@ -97,10 +104,6 @@ const createColliderFromOptions = ({
97
104
  w: qRotation.w
98
105
  }).setRestitution((_options$restitution = options === null || options === void 0 ? void 0 : options.restitution) !== null && _options$restitution !== void 0 ? _options$restitution : 0).setRestitutionCombineRule((_options$restitutionC = options === null || options === void 0 ? void 0 : options.restitutionCombineRule) !== null && _options$restitutionC !== void 0 ? _options$restitutionC : CoefficientCombineRule.Average).setFriction((_options$friction = options === null || options === void 0 ? void 0 : options.friction) !== null && _options$friction !== void 0 ? _options$friction : 0.7).setFrictionCombineRule((_options$frictionComb = options === null || options === void 0 ? void 0 : options.frictionCombineRule) !== null && _options$frictionComb !== void 0 ? _options$frictionComb : CoefficientCombineRule.Average);
99
106
 
100
- if (colliderShape === "heightfield") {
101
- console.log(colliderDesc);
102
- }
103
-
104
107
  if (hasCollisionEvents) {
105
108
  colliderDesc = colliderDesc.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
106
109
  } // If any of the mass properties are specified, add mass properties
@@ -127,6 +130,7 @@ const createColliderFromOptions = ({
127
130
  }
128
131
 
129
132
  const collider = world.createCollider(colliderDesc, rigidBody);
133
+ applyColliderOptions(collider, options);
130
134
  return collider;
131
135
  };
132
136
 
@@ -147,7 +151,6 @@ const createCollidersFromChildren = ({
147
151
  }) => {
148
152
  const hasCollisionEvents = !!(options.onCollisionEnter || options.onCollisionExit);
149
153
  const colliders = [];
150
- new Vector3();
151
154
  object.traverseVisible(child => {
152
155
  if ("isMesh" in child) {
153
156
  if (_ignoreMeshColliders && isChildOfMeshCollider(child)) return;
@@ -193,6 +196,7 @@ const createCollidersFromChildren = ({
193
196
  });
194
197
  const actualRigidBody = rigidBody ? world.getRigidBody(rigidBody.handle) : undefined;
195
198
  const collider = world.createCollider(desc, actualRigidBody);
199
+ applyColliderOptions(collider, options);
196
200
  colliders.push(collider);
197
201
  }
198
202
  });
@@ -303,12 +307,7 @@ const createRigidBodyApi = ref => {
303
307
  addTorque: torque => ref.current().addTorque(torque, true),
304
308
 
305
309
  translation() {
306
- const {
307
- x,
308
- y,
309
- z
310
- } = ref.current().translation();
311
- return new Vector3(x, y, z);
310
+ return rapierVector3ToVector3(ref.current().translation());
312
311
  },
313
312
 
314
313
  setTranslation: translation => ref.current().setTranslation(translation, true),
@@ -323,18 +322,8 @@ const createRigidBodyApi = ref => {
323
322
  return new Quaternion(x, y, z, w);
324
323
  },
325
324
 
326
- setRotation: ({
327
- x,
328
- y,
329
- z
330
- }) => {
331
- const q = vector3ToQuaternion(new Vector3(x, y, z));
332
- ref.current().setRotation({
333
- x: q.x,
334
- y: q.y,
335
- z: q.z,
336
- w: q.w
337
- }, true);
325
+ setRotation: rotation => {
326
+ ref.current().setRotation(rotation, true);
338
327
  },
339
328
 
340
329
  linvel() {
@@ -370,18 +359,8 @@ const createRigidBodyApi = ref => {
370
359
  },
371
360
 
372
361
  setAngularDamping: factor => ref.current().setAngularDamping(factor),
373
- setNextKinematicRotation: ({
374
- x,
375
- y,
376
- z
377
- }) => {
378
- const q = vector3ToQuaternion(new Vector3(x, y, z));
379
- ref.current().setNextKinematicRotation({
380
- x: q.x,
381
- y: q.y,
382
- z: q.z,
383
- w: q.w
384
- });
362
+ setNextKinematicRotation: rotation => {
363
+ ref.current().setNextKinematicRotation(rotation);
385
364
  },
386
365
  setNextKinematicTranslation: translation => ref.current().setNextKinematicTranslation(translation),
387
366
  resetForces: () => ref.current().resetForces(true),
@@ -455,7 +434,8 @@ const Physics = ({
455
434
  children,
456
435
  timeStep: _timeStep = 1 / 60,
457
436
  maxSubSteps: _maxSubSteps = 10,
458
- paused: _paused = false
437
+ paused: _paused = false,
438
+ updatePriority
459
439
  }) => {
460
440
  const rapier = useAsset(importRapier);
461
441
  const [isPaused, setIsPaused] = useState(_paused);
@@ -471,9 +451,9 @@ const Physics = ({
471
451
 
472
452
  return worldRef.current;
473
453
  });
474
- const [colliderMeshes] = useState(() => new Map());
475
454
  const [rigidBodyStates] = useState(() => new Map());
476
455
  const [rigidBodyEvents] = useState(() => new Map());
456
+ const [colliderEvents] = useState(() => new Map());
477
457
  const [eventQueue] = useState(() => new EventQueue(false)); // Init world
478
458
 
479
459
  useEffect(() => {
@@ -503,12 +483,12 @@ const Physics = ({
503
483
  world.timestep = _timeStep;
504
484
  /**
505
485
  * Fixed timeStep simulation progression
506
- * @see https://gafferongames.com/post/fix_your_timestep/
486
+ * @see https://gafferongames.com/post/fix_your_timestep/
507
487
  */
508
488
 
509
489
  let previousTranslations = {}; // don't step time forwards if paused
510
490
 
511
- const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
491
+ const nowTime = steppingState.time += _paused ? 0 : clamp(delta, 0, 1) * 1000;
512
492
  const timeStepMs = _timeStep * 1000;
513
493
  const timeSinceLast = nowTime - steppingState.lastTime;
514
494
  steppingState.lastTime = nowTime;
@@ -558,7 +538,7 @@ const Physics = ({
558
538
  }
559
539
 
560
540
  let oldState = previousTranslations[rigidBody.handle];
561
- let newTranslation = rapierVector3ToVector3(rigidBody.translation());
541
+ let newTranslation = rigidBody.translation();
562
542
  let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
563
543
  let interpolatedTranslation = oldState ? oldState.translation.lerp(newTranslation, 1) : newTranslation;
564
544
  let interpolatedRotation = oldState ? oldState.rotation.slerp(newRotation, interpolationAlpha) : newRotation;
@@ -577,42 +557,71 @@ const Physics = ({
577
557
  const rigidBodyHandle1 = (_collider1$parent = collider1.parent()) === null || _collider1$parent === void 0 ? void 0 : _collider1$parent.handle;
578
558
  const rigidBodyHandle2 = (_collider2$parent = collider2.parent()) === null || _collider2$parent === void 0 ? void 0 : _collider2$parent.handle;
579
559
 
580
- if (!collider1 || !collider2 || !rigidBodyHandle1 || !rigidBodyHandle2) {
560
+ if (!collider1 || !collider2 || rigidBodyHandle1 === undefined || rigidBodyHandle2 === undefined) {
581
561
  return;
582
562
  }
583
563
 
584
564
  const rigidBody1 = world.getRigidBody(rigidBodyHandle1);
585
565
  const rigidBody2 = world.getRigidBody(rigidBodyHandle2);
586
- const events1 = rigidBodyEvents.get(rigidBodyHandle1);
587
- const events2 = rigidBodyEvents.get(rigidBodyHandle2);
566
+ const rigidBody1Events = rigidBodyEvents.get(rigidBodyHandle1);
567
+ const rigidBoyd2Events = rigidBodyEvents.get(rigidBodyHandle2);
568
+ const collider1Events = colliderEvents.get(collider1.handle);
569
+ const collider2Events = colliderEvents.get(collider2.handle);
588
570
 
589
571
  if (started) {
590
572
  world.contactPair(collider1, collider2, (manifold, flipped) => {
591
- var _events1$onCollisionE, _events2$onCollisionE;
573
+ var _rigidBody1Events$onC, _rigidBoyd2Events$onC, _collider1Events$onCo, _collider2Events$onCo;
574
+
575
+ /* RigidBody events */
576
+ rigidBody1Events === null || rigidBody1Events === void 0 ? void 0 : (_rigidBody1Events$onC = rigidBody1Events.onCollisionEnter) === null || _rigidBody1Events$onC === void 0 ? void 0 : _rigidBody1Events$onC.call(rigidBody1Events, {
577
+ target: rigidBody2,
578
+ collider: collider2,
579
+ manifold,
580
+ flipped
581
+ });
582
+ rigidBoyd2Events === null || rigidBoyd2Events === void 0 ? void 0 : (_rigidBoyd2Events$onC = rigidBoyd2Events.onCollisionEnter) === null || _rigidBoyd2Events$onC === void 0 ? void 0 : _rigidBoyd2Events$onC.call(rigidBoyd2Events, {
583
+ target: rigidBody1,
584
+ collider: collider1,
585
+ manifold,
586
+ flipped
587
+ });
588
+ /* Collider events */
592
589
 
593
- events1 === null || events1 === void 0 ? void 0 : (_events1$onCollisionE = events1.onCollisionEnter) === null || _events1$onCollisionE === void 0 ? void 0 : _events1$onCollisionE.call(events1, {
590
+ collider1Events === null || collider1Events === void 0 ? void 0 : (_collider1Events$onCo = collider1Events.onCollisionEnter) === null || _collider1Events$onCo === void 0 ? void 0 : _collider1Events$onCo.call(collider1Events, {
594
591
  target: rigidBody2,
592
+ collider: collider2,
595
593
  manifold,
596
594
  flipped
597
595
  });
598
- events2 === null || events2 === void 0 ? void 0 : (_events2$onCollisionE = events2.onCollisionEnter) === null || _events2$onCollisionE === void 0 ? void 0 : _events2$onCollisionE.call(events2, {
596
+ collider2Events === null || collider2Events === void 0 ? void 0 : (_collider2Events$onCo = collider2Events.onCollisionEnter) === null || _collider2Events$onCo === void 0 ? void 0 : _collider2Events$onCo.call(collider2Events, {
599
597
  target: rigidBody1,
598
+ collider: collider1,
600
599
  manifold,
601
600
  flipped
602
601
  });
603
602
  });
604
603
  } else {
605
- var _events1$onCollisionE2, _events2$onCollisionE2;
604
+ var _rigidBody1Events$onC2, _rigidBoyd2Events$onC2, _collider1Events$onCo2, _collider2Events$onCo2;
606
605
 
607
- events1 === null || events1 === void 0 ? void 0 : (_events1$onCollisionE2 = events1.onCollisionExit) === null || _events1$onCollisionE2 === void 0 ? void 0 : _events1$onCollisionE2.call(events1, {
608
- target: rigidBody2
606
+ rigidBody1Events === null || rigidBody1Events === void 0 ? void 0 : (_rigidBody1Events$onC2 = rigidBody1Events.onCollisionExit) === null || _rigidBody1Events$onC2 === void 0 ? void 0 : _rigidBody1Events$onC2.call(rigidBody1Events, {
607
+ target: rigidBody2,
608
+ collider: collider2
609
+ });
610
+ rigidBoyd2Events === null || rigidBoyd2Events === void 0 ? void 0 : (_rigidBoyd2Events$onC2 = rigidBoyd2Events.onCollisionExit) === null || _rigidBoyd2Events$onC2 === void 0 ? void 0 : _rigidBoyd2Events$onC2.call(rigidBoyd2Events, {
611
+ target: rigidBody1,
612
+ collider: collider1
609
613
  });
610
- events2 === null || events2 === void 0 ? void 0 : (_events2$onCollisionE2 = events2.onCollisionExit) === null || _events2$onCollisionE2 === void 0 ? void 0 : _events2$onCollisionE2.call(events2, {
611
- target: rigidBody1
614
+ collider1Events === null || collider1Events === void 0 ? void 0 : (_collider1Events$onCo2 = collider1Events.onCollisionExit) === null || _collider1Events$onCo2 === void 0 ? void 0 : _collider1Events$onCo2.call(collider1Events, {
615
+ target: rigidBody2,
616
+ collider: collider2
617
+ });
618
+ collider2Events === null || collider2Events === void 0 ? void 0 : (_collider2Events$onCo2 = collider2Events.onCollisionExit) === null || _collider2Events$onCo2 === void 0 ? void 0 : _collider2Events$onCo2.call(collider2Events, {
619
+ target: rigidBody1,
620
+ collider: collider1
612
621
  });
613
622
  }
614
623
  });
615
- });
624
+ }, updatePriority);
616
625
  const api = useMemo(() => createWorldApi(getWorldRef), []);
617
626
  const context = useMemo(() => ({
618
627
  rapier,
@@ -621,9 +630,9 @@ const Physics = ({
621
630
  colliders: _colliders,
622
631
  gravity: _gravity
623
632
  },
624
- colliderMeshes,
625
633
  rigidBodyStates,
626
634
  rigidBodyEvents,
635
+ colliderEvents,
627
636
  isPaused
628
637
  }), [isPaused]);
629
638
  return /*#__PURE__*/React.createElement(RapierContext.Provider, {
@@ -1289,22 +1298,26 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1289
1298
  }, props.children));
1290
1299
  });
1291
1300
 
1292
- const _excluded = ["children"];
1301
+ const _excluded = ["children", "onCollisionEnter", "onCollisionExit"];
1293
1302
 
1294
1303
  const AnyCollider = _ref => {
1295
1304
  let {
1296
- children
1305
+ children,
1306
+ onCollisionEnter,
1307
+ onCollisionExit
1297
1308
  } = _ref,
1298
1309
  props = _objectWithoutProperties(_ref, _excluded);
1299
1310
 
1300
1311
  const {
1301
- world
1312
+ world,
1313
+ colliderEvents
1302
1314
  } = useRapier();
1303
1315
  const rigidBodyContext = useRigidBodyContext();
1304
1316
  const ref = useRef(null);
1305
1317
  useEffect(() => {
1306
1318
  const scale = ref.current.getWorldScale(new Vector3());
1307
- const colliders = []; // If this is an InstancedRigidBody api
1319
+ const colliders = [];
1320
+ const hasCollisionEvents = (rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents) || !!onCollisionEnter || !!onCollisionExit; // If this is an InstancedRigidBody api
1308
1321
 
1309
1322
  if (rigidBodyContext && "at" in rigidBodyContext.api) {
1310
1323
  rigidBodyContext.api.forEach((body, index) => {
@@ -1317,26 +1330,41 @@ const AnyCollider = _ref => {
1317
1330
  }
1318
1331
 
1319
1332
  colliders.push(createColliderFromOptions({
1320
- options: props,
1333
+ options: _objectSpread2({
1334
+ solverGroups: rigidBodyContext.options.solverGroups,
1335
+ collisionGroups: rigidBodyContext.options.collisionGroups
1336
+ }, props),
1321
1337
  world,
1322
1338
  rigidBody: body.raw(),
1323
1339
  scale: instanceScale,
1324
- hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1340
+ hasCollisionEvents
1325
1341
  }));
1326
1342
  });
1327
1343
  } else {
1328
1344
  colliders.push(createColliderFromOptions({
1329
- options: props,
1345
+ options: _objectSpread2({
1346
+ solverGroups: (rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options.solverGroups) || props.solverGroups,
1347
+ collisionGroups: (rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options.collisionGroups) || props.collisionGroups
1348
+ }, props),
1330
1349
  world,
1331
1350
  // Initiate with a rigidbody, or undefined, because colliders can exist without a rigid body
1332
1351
  rigidBody: rigidBodyContext && "raw" in rigidBodyContext.api ? rigidBodyContext.api.raw() : undefined,
1333
1352
  scale,
1334
- hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1353
+ hasCollisionEvents
1335
1354
  }));
1336
1355
  }
1356
+ /* Register collision events. */
1337
1357
 
1358
+
1359
+ colliders.forEach(collider => colliderEvents.set(collider.handle, {
1360
+ onCollisionEnter,
1361
+ onCollisionExit
1362
+ }));
1338
1363
  return () => {
1339
- colliders.forEach(collider => world.removeCollider(collider));
1364
+ colliders.forEach(collider => {
1365
+ colliderEvents.delete(collider.handle);
1366
+ world.removeCollider(collider);
1367
+ });
1340
1368
  };
1341
1369
  }, []);
1342
1370
  return /*#__PURE__*/React.createElement("object3D", {
@@ -1390,4 +1418,40 @@ const ConvexHullCollider = props => {
1390
1418
  }));
1391
1419
  };
1392
1420
 
1393
- export { BallCollider, CapsuleCollider, ConeCollider, ConvexHullCollider, CuboidCollider, CylinderCollider, Debug, HeightfieldCollider, InstancedRigidBodies, MeshCollider, Physics, RigidBody, RoundCuboidCollider, TrimeshCollider, useFixedJoint, useImpulseJoint, usePrismaticJoint, useRapier, useRevoluteJoint, useRigidBody, useSphericalJoint };
1421
+ /**
1422
+ * Calculates an InteractionGroup bitmask for use in the `collisionGroups` or `solverGroups`
1423
+ * properties of RigidBody or Collider components. The first argument represents a list of
1424
+ * groups the entity is in (expressed as numbers from 0 to 15). The second argument is a list
1425
+ * of groups that will be filtered against. When it is omitted, all groups are filtered against.
1426
+ *
1427
+ * @example
1428
+ * A RigidBody that is member of group 0 and will collide with everything from groups 0 and 1:
1429
+ *
1430
+ * ```tsx
1431
+ * <RigidBody collisionGroups={interactionGroups([0], [0, 1])} />
1432
+ * ```
1433
+ *
1434
+ * A RigidBody that is member of groups 0 and 1 and will collide with everything else:
1435
+ *
1436
+ * ```tsx
1437
+ * <RigidBody collisionGroups={interactionGroups([0, 1])} />
1438
+ * ```
1439
+ *
1440
+ * A RigidBody that is member of groups 0 and 1 and will not collide with anything:
1441
+ *
1442
+ * ```tsx
1443
+ * <RigidBody collisionGroups={interactionGroups([0, 1], [])} />
1444
+ * ```
1445
+ *
1446
+ * Please note that Rapier needs interaction filters to evaluate to true between _both_ colliding
1447
+ * entities for collision events to trigger.
1448
+ *
1449
+ * @param memberships Groups the collider is a member of. (Values can range from 0 to 15.)
1450
+ * @param filters Groups the interaction group should filter against. (Values can range from 0 to 15.)
1451
+ * @returns An InteractionGroup bitmask.
1452
+ */
1453
+ const interactionGroups = (memberships, filters) => (bitmask(memberships) << 16) + (filters !== undefined ? bitmask(filters) : 0b1111111111111111);
1454
+
1455
+ const bitmask = groups => [groups].flat().reduce((acc, layer) => acc | 1 << layer, 0);
1456
+
1457
+ export { BallCollider, CapsuleCollider, ConeCollider, ConvexHullCollider, CuboidCollider, CylinderCollider, Debug, HeightfieldCollider, InstancedRigidBodies, MeshCollider, Physics, RigidBody, RoundCuboidCollider, TrimeshCollider, interactionGroups, useFixedJoint, useImpulseJoint, usePrismaticJoint, useRapier, useRevoluteJoint, useRigidBody, useSphericalJoint };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/rapier",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "source": "src/index.ts",
5
5
  "main": "dist/react-three-rapier.cjs.js",
6
6
  "module": "dist/react-three-rapier.esm.js",
@@ -20,12 +20,12 @@
20
20
  "peerDependencies": {
21
21
  "@react-three/fiber": "^8.0.12",
22
22
  "react": "^18.0.0",
23
- "three": "^0.139.2",
24
- "three-stdlib": "^2.15.0"
23
+ "three": ">=0.139.2"
25
24
  },
26
25
  "dependencies": {
27
26
  "@dimforge/rapier3d-compat": "0.9.0",
28
- "use-asset": "^1.0.4"
27
+ "use-asset": "^1.0.4",
28
+ "three-stdlib": "^2.15.0"
29
29
  },
30
30
  "repository": "https://github.com/pmndrs/react-three-rapier/tree/master/packages/react-three-rapier"
31
31
  }
package/readme.md CHANGED
@@ -1,10 +1,10 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/pmndrs/react-three-rapier/HEAD/packages/react-three-rapier/misc/hero.svg" alt="@react-three/rapier" />
2
+ <a href="#"><img src="https://raw.githubusercontent.com/pmndrs/react-three-rapier/HEAD/packages/react-three-rapier/misc/hero.svg" alt="@react-three/rapier" /></a>
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <img src="https://img.shields.io/npm/v/@react-three/rapier?style=for-the-badge&colorA=0099DA&colorB=ffffff" />
7
- <img src="https://img.shields.io/discord/740090768164651008?style=for-the-badge&colorA=0099DA&colorB=ffffff&label=discord&logo=discord&logoColor=ffffff)](https://discord.gg/ZZjjNvJ" />
6
+ <a href="https://www.npmjs.com/package/@react-three/rapier"><img src="https://img.shields.io/npm/v/@react-three/rapier?style=for-the-badge&colorA=0099DA&colorB=ffffff" /></a>
7
+ <a href="https://discord.gg/ZZjjNvJ"><img src="https://img.shields.io/discord/740090768164651008?style=for-the-badge&colorA=0099DA&colorB=ffffff&label=discord&logo=discord&logoColor=ffffff" /></a>
8
8
  </p>
9
9
 
10
10
  <p align="center">⚠️ Under heavy development. All APIs are subject to change. ⚠️</p>
@@ -141,7 +141,7 @@ const Scene = () => {
141
141
  instancedApi.at(40).applyImpulse({ x: 0, y: 10, z: 0 });
142
142
 
143
143
  // Or update all instances as if they were in an array
144
- instancedApi.forEach((api) => {
144
+ instancedApi.forEach(api => {
145
145
  api.applyImpulse({ x: 0, y: 10, z: 0 });
146
146
  });
147
147
  }, []);
@@ -153,13 +153,13 @@ const Scene = () => {
153
153
  const rotations = Array.from({ length: COUNT }, (_, index) => [
154
154
  Math.random(),
155
155
  Math.random(),
156
- Math.random(),
156
+ Math.random()
157
157
  ]);
158
158
 
159
159
  const scales = Array.from({ length: COUNT }, (_, index) => [
160
160
  Math.random(),
161
161
  Math.random(),
162
- Math.random(),
162
+ Math.random()
163
163
  ]);
164
164
 
165
165
  return (
@@ -212,7 +212,7 @@ const Scene = () => {
212
212
 
213
213
  ## Events
214
214
 
215
- You can subscribe collision and state events on the RigidBody.
215
+ You can subscribe to collision and state events on the RigidBody:
216
216
 
217
217
  ```tsx
218
218
  const RigidBottle = () => {
@@ -235,6 +235,60 @@ return (
235
235
  }
236
236
  ```
237
237
 
238
+ You may also subscribe to collision events on individual colliders:
239
+
240
+ ```tsx
241
+ <CuboidCollider
242
+ onCollisionEnter={payload => {
243
+ /* ... */
244
+ }}
245
+ onCollisionExit={payload => {
246
+ /* ... */
247
+ }}
248
+ />
249
+ ```
250
+
251
+ The `payload` object for all collision callbacks contains the following properties:
252
+
253
+ - `target`
254
+ The other rigidbody that was involved in the collision event.
255
+ - `collider`
256
+ The other collider that was involved in the collision event.
257
+ - `manifold` (enter only)
258
+ The [contact manifold](https://rapier.rs/javascript3d/classes/TempContactManifold.html) generated by the collision event.
259
+ - `flipped` (enter only)
260
+ `true` if the data in the `manifold` [is flipped](https://rapier.rs/javascript3d/classes/World.html#contactPair).
261
+
262
+ ### Configuring collision and solver groups
263
+
264
+ Both `<RigidBody>` as well as all collider components allow you to configure `collisionsGroups` and `solverGroups` properties that configures which groups the colliders are in, and what other groups they should interact with in potential collision and solving events (you will find more details on this in the [Rapier documentation](https://rapier.rs/docs/user_guides/javascript/colliders/#collision-groups-and-solver-groups).)
265
+
266
+ Since these are set as bitmasks and bitmasks can get a bit unwieldy to generate, this library provides a helper called `interactionGroups` that can be used to generate bitmasks from numbers and arrays of groups, where groups are identified using numbers from 0 to 15.
267
+
268
+ The first argument is the group, or an array of groups, that the collider is a member of; the second argument is the group, or an array of groups, that the collider should interact with.
269
+
270
+ Here the collider is in group 0, and interacts with colliders from groups 0, 1 and 2:
271
+
272
+ ```tsx
273
+ <CapsuleCollider collisionGroups={interactionGroups(0, [0, 1, 2])} />
274
+ ```
275
+
276
+ This collider is in multiple groups, but only interacts with colliders from a single group:
277
+
278
+ ```tsx
279
+ <CapsuleCollider collisionGroups={interactionGroups([0, 5], 7)} />
280
+ ```
281
+
282
+ When the second argument is omitted, the collider will interact with all groups:
283
+
284
+ ```tsx
285
+ <CapsuleCollider collisionGroups={interactionGroups(12)} />
286
+ ```
287
+
288
+ > **Note** Please remember that in Rapier, for a collision (or solving) event to occur, both colliders involved in the event must match the related interaction groups -- a one-way match will be ignored.
289
+
290
+ > **Note** By default, colliders are members of all groups, and will interact with all other groups.
291
+
238
292
  ## Joints
239
293
 
240
294
  WIP
@@ -255,7 +309,7 @@ In order, but also not necessarily:
255
309
  - [x] Colliders outside RigidBodies
256
310
  - [x] InstancedMesh support
257
311
  - [x] Timestep improvements for determinism
258
- - [ ] Normalize and improve collision events (add events to single Colliders, InstancedRigidBodies, etc)
312
+ - [x] Normalize and improve collision events (add events to single Colliders)
313
+ - [ ] Add collision events to InstancedRigidBodies
259
314
  - [ ] Docs
260
315
  - [ ] CodeSandbox examples
261
- - [ ] Helpers, for things like Vehicle, Rope, Player, etc