@react-three/rapier 0.7.0 → 0.7.2

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.
@@ -63,11 +63,13 @@ interface RapierWorldProps {
63
63
  /**
64
64
  * Set the timestep for the simulation.
65
65
  * Setting this to a number (eg. 1/60) will run the
66
- * simulation at that framerate.
66
+ * simulation at that framerate. Alternatively, you can set this to
67
+ * "vary", which will cause the simulation to always synchronize with
68
+ * the current frame delta times.
67
69
  *
68
70
  * @defaultValue 1/60
69
71
  */
70
- timeStep?: number;
72
+ timeStep?: number | "vary";
71
73
  /**
72
74
  * Pause the physics simulation
73
75
  *
@@ -118,11 +118,11 @@ export interface UseColliderOptions<ColliderArgs extends Array<unknown>> {
118
118
  /**
119
119
  * The position of this collider relative to the rigid body
120
120
  */
121
- position?: Vector3Array;
121
+ position?: Object3DProps['position'];
122
122
  /**
123
123
  * The rotation of this collider relative to the rigid body
124
124
  */
125
- rotation?: Vector3Array;
125
+ rotation?: Object3DProps['rotation'];
126
126
  /**
127
127
  * The rotation, as a Quaternion, of this collider relative to the rigid body
128
128
  */
@@ -219,11 +219,11 @@ export interface UseRigidBodyOptions extends ColliderProps {
219
219
  /**
220
220
  * Initial position of the RigidBody
221
221
  */
222
- position?: Vector3Array;
222
+ position?: Object3DProps['position'];
223
223
  /**
224
224
  * Initial rotation of the RigidBody
225
225
  */
226
- rotation?: Vector3Array;
226
+ rotation?: Object3DProps['rotation'];
227
227
  /**
228
228
  * Automatically generate colliders based on meshes inside this
229
229
  * rigid body.
@@ -14,7 +14,7 @@ interface CreateRigidBodyStateOptions {
14
14
  export declare const createRigidBodyState: ({ rigidBody, object, setMatrix, getMatrix, worldScale }: CreateRigidBodyStateOptions) => RigidBodyState;
15
15
  declare type ImmutableRigidBodyOptions = (keyof RigidBodyProps)[];
16
16
  export declare const immutableRigidBodyOptions: ImmutableRigidBodyOptions;
17
- export declare const setRigidBodyOptions: (rigidBody: RigidBody, options: RigidBodyProps, states: RigidBodyStateMap) => void;
18
- export declare const useUpdateRigidBodyOptions: (rigidBodyRef: MutableRefObject<RigidBody | undefined>, props: RigidBodyProps, states: RigidBodyStateMap) => void;
19
- export declare const useRigidBodyEvents: (rigidBodyRef: MutableRefObject<RigidBody | undefined>, props: RigidBodyProps, events: EventMap) => void;
17
+ export declare const setRigidBodyOptions: (rigidBody: RigidBody, options: RigidBodyProps, states: RigidBodyStateMap, updateTranslations?: boolean) => void;
18
+ export declare const useUpdateRigidBodyOptions: (rigidBodyRef: MutableRefObject<RigidBody | RigidBody[] | undefined>, props: RigidBodyProps, states: RigidBodyStateMap, updateTranslations?: boolean) => void;
19
+ export declare const useRigidBodyEvents: (rigidBodyRef: MutableRefObject<RigidBody | RigidBody[] | undefined>, props: RigidBodyProps, events: EventMap) => void;
20
20
  export {};
@@ -267,27 +267,38 @@ const Physics = ({
267
267
  const [steppingState] = React.useState({
268
268
  accumulator: 0
269
269
  });
270
+ /* Check if the timestep is supposed to be variable. We'll do this here
271
+ once so we don't have to string-check every frame. */
272
+
273
+ const timeStepVariable = _timeStep === "vary";
270
274
  fiber.useFrame((_, dt) => {
271
275
  const world = worldRef.current;
272
276
  if (!world) return;
273
- world.timestep = _timeStep;
274
277
  /**
275
278
  * Fixed timeStep simulation progression
276
279
  * @see https://gafferongames.com/post/fix_your_timestep/
277
280
  */
278
- // don't step time forwards if paused
279
- // Increase accumulator
280
281
 
281
- steppingState.accumulator += _paused ? 0 : MathUtils.clamp(dt, 0, 0.2);
282
+ const clampedDelta = MathUtils.clamp(dt, 0, 0.2);
283
+
284
+ if (timeStepVariable) {
285
+ world.timestep = clampedDelta;
286
+ if (!_paused) world.step(eventQueue);
287
+ } else {
288
+ world.timestep = _timeStep; // don't step time forwards if paused
289
+ // Increase accumulator
282
290
 
283
- if (!_paused) {
284
- while (steppingState.accumulator >= _timeStep) {
285
- world.step(eventQueue);
286
- steppingState.accumulator -= _timeStep;
291
+ steppingState.accumulator += _paused ? 0 : clampedDelta;
292
+
293
+ if (!_paused) {
294
+ while (steppingState.accumulator >= _timeStep) {
295
+ world.step(eventQueue);
296
+ steppingState.accumulator -= _timeStep;
297
+ }
287
298
  }
288
299
  }
289
300
 
290
- const interpolationAlpha = steppingState.accumulator % _timeStep / _timeStep; // Update meshes
301
+ const interpolationAlpha = timeStepVariable ? 1 : steppingState.accumulator % _timeStep / _timeStep; // Update meshes
291
302
 
292
303
  rigidBodyStates.forEach((state, handle) => {
293
304
  const rigidBody = world.getRigidBody(handle);
@@ -636,7 +647,7 @@ const mutableRigidBodyOptions = {
636
647
  }
637
648
  };
638
649
  const mutableRigidBodyOptionKeys = Object.keys(mutableRigidBodyOptions);
639
- const setRigidBodyOptions = (rigidBody, options, states) => {
650
+ const setRigidBodyOptions = (rigidBody, options, states, updateTranslations = true) => {
640
651
  if (!rigidBody) {
641
652
  return;
642
653
  }
@@ -644,12 +655,15 @@ const setRigidBodyOptions = (rigidBody, options, states) => {
644
655
  const state = states.get(rigidBody.handle);
645
656
 
646
657
  if (state) {
647
- state.object.updateWorldMatrix(true, false);
658
+ if (updateTranslations) {
659
+ state.object.updateWorldMatrix(true, false);
660
+
661
+ _matrix4.copy(state.object.matrixWorld).decompose(_position, _rotation, _scale);
648
662
 
649
- _matrix4.copy(state.object.matrixWorld).decompose(_position, _rotation, _scale);
663
+ rigidBody.setTranslation(_position, false);
664
+ rigidBody.setRotation(_rotation, false);
665
+ }
650
666
 
651
- rigidBody.setTranslation(_position, false);
652
- rigidBody.setRotation(_rotation, false);
653
667
  mutableRigidBodyOptionKeys.forEach(key => {
654
668
  if (key in options) {
655
669
  mutableRigidBodyOptions[key](rigidBody, options[key]);
@@ -657,9 +671,15 @@ const setRigidBodyOptions = (rigidBody, options, states) => {
657
671
  });
658
672
  }
659
673
  };
660
- const useUpdateRigidBodyOptions = (rigidBodyRef, props, states) => {
674
+ const useUpdateRigidBodyOptions = (rigidBodyRef, props, states, updateTranslations = true) => {
661
675
  React.useEffect(() => {
662
- setRigidBodyOptions(rigidBodyRef.current, props, states);
676
+ if ("length" in rigidBodyRef.current) {
677
+ rigidBodyRef.current.forEach(rigidBody => {
678
+ setRigidBodyOptions(rigidBody, props, states, updateTranslations);
679
+ });
680
+ } else {
681
+ setRigidBodyOptions(rigidBodyRef.current, props, states, updateTranslations);
682
+ }
663
683
  }, [props]);
664
684
  };
665
685
  const useRigidBodyEvents = (rigidBodyRef, props, events) => {
@@ -671,17 +691,31 @@ const useRigidBodyEvents = (rigidBodyRef, props, events) => {
671
691
  onIntersectionEnter,
672
692
  onIntersectionExit
673
693
  } = props;
694
+ const eventHandlers = {
695
+ onWake,
696
+ onSleep,
697
+ onCollisionEnter,
698
+ onCollisionExit,
699
+ onIntersectionEnter,
700
+ onIntersectionExit
701
+ };
674
702
  React.useEffect(() => {
675
- events.set(rigidBodyRef.current.handle, {
676
- onWake,
677
- onSleep,
678
- onCollisionEnter,
679
- onCollisionExit,
680
- onIntersectionEnter,
681
- onIntersectionExit
682
- });
703
+ if ("length" in rigidBodyRef.current) {
704
+ rigidBodyRef.current.forEach(rigidBody => {
705
+ events.set(rigidBody.handle, eventHandlers);
706
+ });
707
+ } else {
708
+ events.set(rigidBodyRef.current.handle, eventHandlers);
709
+ }
710
+
683
711
  return () => {
684
- events.delete(rigidBodyRef.current.handle);
712
+ if ("length" in rigidBodyRef.current) {
713
+ rigidBodyRef.current.forEach(rigidBody => {
714
+ events.delete(rigidBody.handle);
715
+ });
716
+ } else {
717
+ events.delete(rigidBodyRef.current.handle);
718
+ }
685
719
  };
686
720
  }, [onWake, onSleep, onCollisionEnter, onCollisionExit]);
687
721
  };
@@ -924,7 +958,7 @@ const useChildColliderProps = (ref, options, ignoreMeshColliders = true) => {
924
958
  ignoreMeshColliders
925
959
  }));
926
960
  }
927
- }, [options]);
961
+ }, [options.colliders]);
928
962
  return colliderProps;
929
963
  };
930
964
  const useRigidBody = (options = {}) => {
@@ -1186,12 +1220,13 @@ const RigidBody = /*#__PURE__*/React.forwardRef((props, ref) => {
1186
1220
 
1187
1221
  const [object, api, childColliderProps] = useRigidBody(props);
1188
1222
  React.useImperativeHandle(ref, () => api);
1223
+ const contextValue = React.useMemo(() => ({
1224
+ ref: object,
1225
+ api,
1226
+ options: props
1227
+ }), [object, api, props]);
1189
1228
  return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1190
- value: {
1191
- ref: object,
1192
- api,
1193
- options: props
1194
- }
1229
+ value: contextValue
1195
1230
  }, /*#__PURE__*/React__default["default"].createElement("object3D", _extends({
1196
1231
  ref: object
1197
1232
  }, objectProps, {
@@ -1413,7 +1448,8 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1413
1448
  const {
1414
1449
  world,
1415
1450
  rigidBodyStates,
1416
- physicsOptions
1451
+ physicsOptions,
1452
+ rigidBodyEvents
1417
1453
  } = useRapier();
1418
1454
  const object = React.useRef(null);
1419
1455
 
@@ -1424,7 +1460,8 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1424
1460
  } = props,
1425
1461
  options = _objectWithoutProperties(props, _excluded);
1426
1462
 
1427
- const instancesRef = React.useRef();
1463
+ const instancesRef = React.useRef([]);
1464
+ const rigidBodyRefs = React.useRef([]);
1428
1465
  const instancesRefGetter = React.useRef(() => {
1429
1466
  if (!instancesRef.current) {
1430
1467
  instancesRef.current = [];
@@ -1438,7 +1475,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1438
1475
  const childColliderProps = useChildColliderProps(object, mergedOptions);
1439
1476
  React.useLayoutEffect(() => {
1440
1477
  object.current.updateWorldMatrix(true, false);
1441
- const rigidBodies = instancesRefGetter.current();
1478
+ const instances = instancesRefGetter.current();
1442
1479
  const invertedWorld = object.current.matrixWorld.clone().invert();
1443
1480
  object.current.traverseVisible(mesh => {
1444
1481
  if (mesh instanceof three.InstancedMesh) {
@@ -1450,6 +1487,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1450
1487
 
1451
1488
  const desc = rigidBodyDescFromOptions(props);
1452
1489
  const rigidBody = world.createRigidBody(desc);
1490
+ rigidBodyRefs.current.push(rigidBody);
1453
1491
  const scale = ((_options$scales = options.scales) === null || _options$scales === void 0 ? void 0 : _options$scales[index]) || [1, 1, 1];
1454
1492
  const instanceScale = worldScale.clone().multiply(vectorArrayToVector3(scale));
1455
1493
  rigidBodyStates.set(rigidBody.handle, createRigidBodyState({
@@ -1469,10 +1507,9 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1469
1507
 
1470
1508
  _object3d.rotation.set(rx, ry, rz);
1471
1509
 
1472
- _object3d.applyMatrix4(invertedWorld); // Set initial transforms based on world transforms
1473
- // will be replaced by the setRigidBodyOption below
1474
-
1510
+ _object3d.applyMatrix4(invertedWorld);
1475
1511
 
1512
+ mesh.setMatrixAt(index, _object3d.matrix);
1476
1513
  rigidBody.setTranslation(_object3d.position, false);
1477
1514
  rigidBody.setRotation(_object3d.quaternion, false);
1478
1515
  const api = createRigidBodyApi({
@@ -1481,7 +1518,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1481
1518
  }
1482
1519
 
1483
1520
  });
1484
- rigidBodies.push({
1521
+ instances.push({
1485
1522
  rigidBody,
1486
1523
  api
1487
1524
  });
@@ -1489,21 +1526,27 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1489
1526
  }
1490
1527
  });
1491
1528
  return () => {
1492
- rigidBodies.forEach(rb => {
1529
+ instances.forEach(rb => {
1493
1530
  world.removeRigidBody(rb.rigidBody);
1494
1531
  rigidBodyStates.delete(rb.rigidBody.handle);
1495
1532
  });
1496
- instancesRef.current = undefined;
1533
+ rigidBodyRefs.current = [];
1534
+ instancesRef.current = [];
1497
1535
  };
1498
1536
  }, []);
1499
1537
  const api = React.useMemo(() => createInstancedRigidBodiesApi(instancesRefGetter), []);
1500
1538
  React.useImperativeHandle(ref, () => api);
1501
- return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1502
- value: {
1539
+ useUpdateRigidBodyOptions(rigidBodyRefs, mergedOptions, rigidBodyStates, false);
1540
+ useRigidBodyEvents(rigidBodyRefs, mergedOptions, rigidBodyEvents);
1541
+ const contextValue = React.useMemo(() => {
1542
+ return {
1503
1543
  ref: object,
1504
1544
  api,
1505
- options: props
1506
- }
1545
+ options: mergedOptions
1546
+ };
1547
+ }, [api, mergedOptions]);
1548
+ return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1549
+ value: contextValue
1507
1550
  }, /*#__PURE__*/React__default["default"].createElement("object3D", {
1508
1551
  ref: object
1509
1552
  }, props.children, childColliderProps.map((colliderProps, index) => /*#__PURE__*/React__default["default"].createElement(AnyCollider, _extends({
@@ -267,27 +267,38 @@ const Physics = ({
267
267
  const [steppingState] = React.useState({
268
268
  accumulator: 0
269
269
  });
270
+ /* Check if the timestep is supposed to be variable. We'll do this here
271
+ once so we don't have to string-check every frame. */
272
+
273
+ const timeStepVariable = _timeStep === "vary";
270
274
  fiber.useFrame((_, dt) => {
271
275
  const world = worldRef.current;
272
276
  if (!world) return;
273
- world.timestep = _timeStep;
274
277
  /**
275
278
  * Fixed timeStep simulation progression
276
279
  * @see https://gafferongames.com/post/fix_your_timestep/
277
280
  */
278
- // don't step time forwards if paused
279
- // Increase accumulator
280
281
 
281
- steppingState.accumulator += _paused ? 0 : MathUtils.clamp(dt, 0, 0.2);
282
+ const clampedDelta = MathUtils.clamp(dt, 0, 0.2);
283
+
284
+ if (timeStepVariable) {
285
+ world.timestep = clampedDelta;
286
+ if (!_paused) world.step(eventQueue);
287
+ } else {
288
+ world.timestep = _timeStep; // don't step time forwards if paused
289
+ // Increase accumulator
282
290
 
283
- if (!_paused) {
284
- while (steppingState.accumulator >= _timeStep) {
285
- world.step(eventQueue);
286
- steppingState.accumulator -= _timeStep;
291
+ steppingState.accumulator += _paused ? 0 : clampedDelta;
292
+
293
+ if (!_paused) {
294
+ while (steppingState.accumulator >= _timeStep) {
295
+ world.step(eventQueue);
296
+ steppingState.accumulator -= _timeStep;
297
+ }
287
298
  }
288
299
  }
289
300
 
290
- const interpolationAlpha = steppingState.accumulator % _timeStep / _timeStep; // Update meshes
301
+ const interpolationAlpha = timeStepVariable ? 1 : steppingState.accumulator % _timeStep / _timeStep; // Update meshes
291
302
 
292
303
  rigidBodyStates.forEach((state, handle) => {
293
304
  const rigidBody = world.getRigidBody(handle);
@@ -636,7 +647,7 @@ const mutableRigidBodyOptions = {
636
647
  }
637
648
  };
638
649
  const mutableRigidBodyOptionKeys = Object.keys(mutableRigidBodyOptions);
639
- const setRigidBodyOptions = (rigidBody, options, states) => {
650
+ const setRigidBodyOptions = (rigidBody, options, states, updateTranslations = true) => {
640
651
  if (!rigidBody) {
641
652
  return;
642
653
  }
@@ -644,12 +655,15 @@ const setRigidBodyOptions = (rigidBody, options, states) => {
644
655
  const state = states.get(rigidBody.handle);
645
656
 
646
657
  if (state) {
647
- state.object.updateWorldMatrix(true, false);
658
+ if (updateTranslations) {
659
+ state.object.updateWorldMatrix(true, false);
660
+
661
+ _matrix4.copy(state.object.matrixWorld).decompose(_position, _rotation, _scale);
648
662
 
649
- _matrix4.copy(state.object.matrixWorld).decompose(_position, _rotation, _scale);
663
+ rigidBody.setTranslation(_position, false);
664
+ rigidBody.setRotation(_rotation, false);
665
+ }
650
666
 
651
- rigidBody.setTranslation(_position, false);
652
- rigidBody.setRotation(_rotation, false);
653
667
  mutableRigidBodyOptionKeys.forEach(key => {
654
668
  if (key in options) {
655
669
  mutableRigidBodyOptions[key](rigidBody, options[key]);
@@ -657,9 +671,15 @@ const setRigidBodyOptions = (rigidBody, options, states) => {
657
671
  });
658
672
  }
659
673
  };
660
- const useUpdateRigidBodyOptions = (rigidBodyRef, props, states) => {
674
+ const useUpdateRigidBodyOptions = (rigidBodyRef, props, states, updateTranslations = true) => {
661
675
  React.useEffect(() => {
662
- setRigidBodyOptions(rigidBodyRef.current, props, states);
676
+ if ("length" in rigidBodyRef.current) {
677
+ rigidBodyRef.current.forEach(rigidBody => {
678
+ setRigidBodyOptions(rigidBody, props, states, updateTranslations);
679
+ });
680
+ } else {
681
+ setRigidBodyOptions(rigidBodyRef.current, props, states, updateTranslations);
682
+ }
663
683
  }, [props]);
664
684
  };
665
685
  const useRigidBodyEvents = (rigidBodyRef, props, events) => {
@@ -671,17 +691,31 @@ const useRigidBodyEvents = (rigidBodyRef, props, events) => {
671
691
  onIntersectionEnter,
672
692
  onIntersectionExit
673
693
  } = props;
694
+ const eventHandlers = {
695
+ onWake,
696
+ onSleep,
697
+ onCollisionEnter,
698
+ onCollisionExit,
699
+ onIntersectionEnter,
700
+ onIntersectionExit
701
+ };
674
702
  React.useEffect(() => {
675
- events.set(rigidBodyRef.current.handle, {
676
- onWake,
677
- onSleep,
678
- onCollisionEnter,
679
- onCollisionExit,
680
- onIntersectionEnter,
681
- onIntersectionExit
682
- });
703
+ if ("length" in rigidBodyRef.current) {
704
+ rigidBodyRef.current.forEach(rigidBody => {
705
+ events.set(rigidBody.handle, eventHandlers);
706
+ });
707
+ } else {
708
+ events.set(rigidBodyRef.current.handle, eventHandlers);
709
+ }
710
+
683
711
  return () => {
684
- events.delete(rigidBodyRef.current.handle);
712
+ if ("length" in rigidBodyRef.current) {
713
+ rigidBodyRef.current.forEach(rigidBody => {
714
+ events.delete(rigidBody.handle);
715
+ });
716
+ } else {
717
+ events.delete(rigidBodyRef.current.handle);
718
+ }
685
719
  };
686
720
  }, [onWake, onSleep, onCollisionEnter, onCollisionExit]);
687
721
  };
@@ -924,7 +958,7 @@ const useChildColliderProps = (ref, options, ignoreMeshColliders = true) => {
924
958
  ignoreMeshColliders
925
959
  }));
926
960
  }
927
- }, [options]);
961
+ }, [options.colliders]);
928
962
  return colliderProps;
929
963
  };
930
964
  const useRigidBody = (options = {}) => {
@@ -1186,12 +1220,13 @@ const RigidBody = /*#__PURE__*/React.forwardRef((props, ref) => {
1186
1220
 
1187
1221
  const [object, api, childColliderProps] = useRigidBody(props);
1188
1222
  React.useImperativeHandle(ref, () => api);
1223
+ const contextValue = React.useMemo(() => ({
1224
+ ref: object,
1225
+ api,
1226
+ options: props
1227
+ }), [object, api, props]);
1189
1228
  return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1190
- value: {
1191
- ref: object,
1192
- api,
1193
- options: props
1194
- }
1229
+ value: contextValue
1195
1230
  }, /*#__PURE__*/React__default["default"].createElement("object3D", _extends({
1196
1231
  ref: object
1197
1232
  }, objectProps, {
@@ -1413,7 +1448,8 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1413
1448
  const {
1414
1449
  world,
1415
1450
  rigidBodyStates,
1416
- physicsOptions
1451
+ physicsOptions,
1452
+ rigidBodyEvents
1417
1453
  } = useRapier();
1418
1454
  const object = React.useRef(null);
1419
1455
 
@@ -1424,7 +1460,8 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1424
1460
  } = props,
1425
1461
  options = _objectWithoutProperties(props, _excluded);
1426
1462
 
1427
- const instancesRef = React.useRef();
1463
+ const instancesRef = React.useRef([]);
1464
+ const rigidBodyRefs = React.useRef([]);
1428
1465
  const instancesRefGetter = React.useRef(() => {
1429
1466
  if (!instancesRef.current) {
1430
1467
  instancesRef.current = [];
@@ -1438,7 +1475,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1438
1475
  const childColliderProps = useChildColliderProps(object, mergedOptions);
1439
1476
  React.useLayoutEffect(() => {
1440
1477
  object.current.updateWorldMatrix(true, false);
1441
- const rigidBodies = instancesRefGetter.current();
1478
+ const instances = instancesRefGetter.current();
1442
1479
  const invertedWorld = object.current.matrixWorld.clone().invert();
1443
1480
  object.current.traverseVisible(mesh => {
1444
1481
  if (mesh instanceof three.InstancedMesh) {
@@ -1450,6 +1487,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1450
1487
 
1451
1488
  const desc = rigidBodyDescFromOptions(props);
1452
1489
  const rigidBody = world.createRigidBody(desc);
1490
+ rigidBodyRefs.current.push(rigidBody);
1453
1491
  const scale = ((_options$scales = options.scales) === null || _options$scales === void 0 ? void 0 : _options$scales[index]) || [1, 1, 1];
1454
1492
  const instanceScale = worldScale.clone().multiply(vectorArrayToVector3(scale));
1455
1493
  rigidBodyStates.set(rigidBody.handle, createRigidBodyState({
@@ -1469,10 +1507,9 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1469
1507
 
1470
1508
  _object3d.rotation.set(rx, ry, rz);
1471
1509
 
1472
- _object3d.applyMatrix4(invertedWorld); // Set initial transforms based on world transforms
1473
- // will be replaced by the setRigidBodyOption below
1474
-
1510
+ _object3d.applyMatrix4(invertedWorld);
1475
1511
 
1512
+ mesh.setMatrixAt(index, _object3d.matrix);
1476
1513
  rigidBody.setTranslation(_object3d.position, false);
1477
1514
  rigidBody.setRotation(_object3d.quaternion, false);
1478
1515
  const api = createRigidBodyApi({
@@ -1481,7 +1518,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1481
1518
  }
1482
1519
 
1483
1520
  });
1484
- rigidBodies.push({
1521
+ instances.push({
1485
1522
  rigidBody,
1486
1523
  api
1487
1524
  });
@@ -1489,21 +1526,27 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1489
1526
  }
1490
1527
  });
1491
1528
  return () => {
1492
- rigidBodies.forEach(rb => {
1529
+ instances.forEach(rb => {
1493
1530
  world.removeRigidBody(rb.rigidBody);
1494
1531
  rigidBodyStates.delete(rb.rigidBody.handle);
1495
1532
  });
1496
- instancesRef.current = undefined;
1533
+ rigidBodyRefs.current = [];
1534
+ instancesRef.current = [];
1497
1535
  };
1498
1536
  }, []);
1499
1537
  const api = React.useMemo(() => createInstancedRigidBodiesApi(instancesRefGetter), []);
1500
1538
  React.useImperativeHandle(ref, () => api);
1501
- return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1502
- value: {
1539
+ useUpdateRigidBodyOptions(rigidBodyRefs, mergedOptions, rigidBodyStates, false);
1540
+ useRigidBodyEvents(rigidBodyRefs, mergedOptions, rigidBodyEvents);
1541
+ const contextValue = React.useMemo(() => {
1542
+ return {
1503
1543
  ref: object,
1504
1544
  api,
1505
- options: props
1506
- }
1545
+ options: mergedOptions
1546
+ };
1547
+ }, [api, mergedOptions]);
1548
+ return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1549
+ value: contextValue
1507
1550
  }, /*#__PURE__*/React__default["default"].createElement("object3D", {
1508
1551
  ref: object
1509
1552
  }, props.children, childColliderProps.map((colliderProps, index) => /*#__PURE__*/React__default["default"].createElement(AnyCollider, _extends({
@@ -242,27 +242,38 @@ const Physics = ({
242
242
  const [steppingState] = useState({
243
243
  accumulator: 0
244
244
  });
245
+ /* Check if the timestep is supposed to be variable. We'll do this here
246
+ once so we don't have to string-check every frame. */
247
+
248
+ const timeStepVariable = _timeStep === "vary";
245
249
  useFrame((_, dt) => {
246
250
  const world = worldRef.current;
247
251
  if (!world) return;
248
- world.timestep = _timeStep;
249
252
  /**
250
253
  * Fixed timeStep simulation progression
251
254
  * @see https://gafferongames.com/post/fix_your_timestep/
252
255
  */
253
- // don't step time forwards if paused
254
- // Increase accumulator
255
256
 
256
- steppingState.accumulator += _paused ? 0 : clamp(dt, 0, 0.2);
257
+ const clampedDelta = clamp(dt, 0, 0.2);
258
+
259
+ if (timeStepVariable) {
260
+ world.timestep = clampedDelta;
261
+ if (!_paused) world.step(eventQueue);
262
+ } else {
263
+ world.timestep = _timeStep; // don't step time forwards if paused
264
+ // Increase accumulator
257
265
 
258
- if (!_paused) {
259
- while (steppingState.accumulator >= _timeStep) {
260
- world.step(eventQueue);
261
- steppingState.accumulator -= _timeStep;
266
+ steppingState.accumulator += _paused ? 0 : clampedDelta;
267
+
268
+ if (!_paused) {
269
+ while (steppingState.accumulator >= _timeStep) {
270
+ world.step(eventQueue);
271
+ steppingState.accumulator -= _timeStep;
272
+ }
262
273
  }
263
274
  }
264
275
 
265
- const interpolationAlpha = steppingState.accumulator % _timeStep / _timeStep; // Update meshes
276
+ const interpolationAlpha = timeStepVariable ? 1 : steppingState.accumulator % _timeStep / _timeStep; // Update meshes
266
277
 
267
278
  rigidBodyStates.forEach((state, handle) => {
268
279
  const rigidBody = world.getRigidBody(handle);
@@ -611,7 +622,7 @@ const mutableRigidBodyOptions = {
611
622
  }
612
623
  };
613
624
  const mutableRigidBodyOptionKeys = Object.keys(mutableRigidBodyOptions);
614
- const setRigidBodyOptions = (rigidBody, options, states) => {
625
+ const setRigidBodyOptions = (rigidBody, options, states, updateTranslations = true) => {
615
626
  if (!rigidBody) {
616
627
  return;
617
628
  }
@@ -619,12 +630,15 @@ const setRigidBodyOptions = (rigidBody, options, states) => {
619
630
  const state = states.get(rigidBody.handle);
620
631
 
621
632
  if (state) {
622
- state.object.updateWorldMatrix(true, false);
633
+ if (updateTranslations) {
634
+ state.object.updateWorldMatrix(true, false);
635
+
636
+ _matrix4.copy(state.object.matrixWorld).decompose(_position, _rotation, _scale);
623
637
 
624
- _matrix4.copy(state.object.matrixWorld).decompose(_position, _rotation, _scale);
638
+ rigidBody.setTranslation(_position, false);
639
+ rigidBody.setRotation(_rotation, false);
640
+ }
625
641
 
626
- rigidBody.setTranslation(_position, false);
627
- rigidBody.setRotation(_rotation, false);
628
642
  mutableRigidBodyOptionKeys.forEach(key => {
629
643
  if (key in options) {
630
644
  mutableRigidBodyOptions[key](rigidBody, options[key]);
@@ -632,9 +646,15 @@ const setRigidBodyOptions = (rigidBody, options, states) => {
632
646
  });
633
647
  }
634
648
  };
635
- const useUpdateRigidBodyOptions = (rigidBodyRef, props, states) => {
649
+ const useUpdateRigidBodyOptions = (rigidBodyRef, props, states, updateTranslations = true) => {
636
650
  useEffect(() => {
637
- setRigidBodyOptions(rigidBodyRef.current, props, states);
651
+ if ("length" in rigidBodyRef.current) {
652
+ rigidBodyRef.current.forEach(rigidBody => {
653
+ setRigidBodyOptions(rigidBody, props, states, updateTranslations);
654
+ });
655
+ } else {
656
+ setRigidBodyOptions(rigidBodyRef.current, props, states, updateTranslations);
657
+ }
638
658
  }, [props]);
639
659
  };
640
660
  const useRigidBodyEvents = (rigidBodyRef, props, events) => {
@@ -646,17 +666,31 @@ const useRigidBodyEvents = (rigidBodyRef, props, events) => {
646
666
  onIntersectionEnter,
647
667
  onIntersectionExit
648
668
  } = props;
669
+ const eventHandlers = {
670
+ onWake,
671
+ onSleep,
672
+ onCollisionEnter,
673
+ onCollisionExit,
674
+ onIntersectionEnter,
675
+ onIntersectionExit
676
+ };
649
677
  useEffect(() => {
650
- events.set(rigidBodyRef.current.handle, {
651
- onWake,
652
- onSleep,
653
- onCollisionEnter,
654
- onCollisionExit,
655
- onIntersectionEnter,
656
- onIntersectionExit
657
- });
678
+ if ("length" in rigidBodyRef.current) {
679
+ rigidBodyRef.current.forEach(rigidBody => {
680
+ events.set(rigidBody.handle, eventHandlers);
681
+ });
682
+ } else {
683
+ events.set(rigidBodyRef.current.handle, eventHandlers);
684
+ }
685
+
658
686
  return () => {
659
- events.delete(rigidBodyRef.current.handle);
687
+ if ("length" in rigidBodyRef.current) {
688
+ rigidBodyRef.current.forEach(rigidBody => {
689
+ events.delete(rigidBody.handle);
690
+ });
691
+ } else {
692
+ events.delete(rigidBodyRef.current.handle);
693
+ }
660
694
  };
661
695
  }, [onWake, onSleep, onCollisionEnter, onCollisionExit]);
662
696
  };
@@ -899,7 +933,7 @@ const useChildColliderProps = (ref, options, ignoreMeshColliders = true) => {
899
933
  ignoreMeshColliders
900
934
  }));
901
935
  }
902
- }, [options]);
936
+ }, [options.colliders]);
903
937
  return colliderProps;
904
938
  };
905
939
  const useRigidBody = (options = {}) => {
@@ -1161,12 +1195,13 @@ const RigidBody = /*#__PURE__*/forwardRef((props, ref) => {
1161
1195
 
1162
1196
  const [object, api, childColliderProps] = useRigidBody(props);
1163
1197
  useImperativeHandle(ref, () => api);
1198
+ const contextValue = useMemo(() => ({
1199
+ ref: object,
1200
+ api,
1201
+ options: props
1202
+ }), [object, api, props]);
1164
1203
  return /*#__PURE__*/React.createElement(RigidBodyContext.Provider, {
1165
- value: {
1166
- ref: object,
1167
- api,
1168
- options: props
1169
- }
1204
+ value: contextValue
1170
1205
  }, /*#__PURE__*/React.createElement("object3D", _extends({
1171
1206
  ref: object
1172
1207
  }, objectProps, {
@@ -1388,7 +1423,8 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1388
1423
  const {
1389
1424
  world,
1390
1425
  rigidBodyStates,
1391
- physicsOptions
1426
+ physicsOptions,
1427
+ rigidBodyEvents
1392
1428
  } = useRapier();
1393
1429
  const object = useRef(null);
1394
1430
 
@@ -1399,7 +1435,8 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1399
1435
  } = props,
1400
1436
  options = _objectWithoutProperties(props, _excluded);
1401
1437
 
1402
- const instancesRef = useRef();
1438
+ const instancesRef = useRef([]);
1439
+ const rigidBodyRefs = useRef([]);
1403
1440
  const instancesRefGetter = useRef(() => {
1404
1441
  if (!instancesRef.current) {
1405
1442
  instancesRef.current = [];
@@ -1413,7 +1450,7 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1413
1450
  const childColliderProps = useChildColliderProps(object, mergedOptions);
1414
1451
  useLayoutEffect(() => {
1415
1452
  object.current.updateWorldMatrix(true, false);
1416
- const rigidBodies = instancesRefGetter.current();
1453
+ const instances = instancesRefGetter.current();
1417
1454
  const invertedWorld = object.current.matrixWorld.clone().invert();
1418
1455
  object.current.traverseVisible(mesh => {
1419
1456
  if (mesh instanceof InstancedMesh) {
@@ -1425,6 +1462,7 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1425
1462
 
1426
1463
  const desc = rigidBodyDescFromOptions(props);
1427
1464
  const rigidBody = world.createRigidBody(desc);
1465
+ rigidBodyRefs.current.push(rigidBody);
1428
1466
  const scale = ((_options$scales = options.scales) === null || _options$scales === void 0 ? void 0 : _options$scales[index]) || [1, 1, 1];
1429
1467
  const instanceScale = worldScale.clone().multiply(vectorArrayToVector3(scale));
1430
1468
  rigidBodyStates.set(rigidBody.handle, createRigidBodyState({
@@ -1444,10 +1482,9 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1444
1482
 
1445
1483
  _object3d.rotation.set(rx, ry, rz);
1446
1484
 
1447
- _object3d.applyMatrix4(invertedWorld); // Set initial transforms based on world transforms
1448
- // will be replaced by the setRigidBodyOption below
1449
-
1485
+ _object3d.applyMatrix4(invertedWorld);
1450
1486
 
1487
+ mesh.setMatrixAt(index, _object3d.matrix);
1451
1488
  rigidBody.setTranslation(_object3d.position, false);
1452
1489
  rigidBody.setRotation(_object3d.quaternion, false);
1453
1490
  const api = createRigidBodyApi({
@@ -1456,7 +1493,7 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1456
1493
  }
1457
1494
 
1458
1495
  });
1459
- rigidBodies.push({
1496
+ instances.push({
1460
1497
  rigidBody,
1461
1498
  api
1462
1499
  });
@@ -1464,21 +1501,27 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1464
1501
  }
1465
1502
  });
1466
1503
  return () => {
1467
- rigidBodies.forEach(rb => {
1504
+ instances.forEach(rb => {
1468
1505
  world.removeRigidBody(rb.rigidBody);
1469
1506
  rigidBodyStates.delete(rb.rigidBody.handle);
1470
1507
  });
1471
- instancesRef.current = undefined;
1508
+ rigidBodyRefs.current = [];
1509
+ instancesRef.current = [];
1472
1510
  };
1473
1511
  }, []);
1474
1512
  const api = useMemo(() => createInstancedRigidBodiesApi(instancesRefGetter), []);
1475
1513
  useImperativeHandle(ref, () => api);
1476
- return /*#__PURE__*/React.createElement(RigidBodyContext.Provider, {
1477
- value: {
1514
+ useUpdateRigidBodyOptions(rigidBodyRefs, mergedOptions, rigidBodyStates, false);
1515
+ useRigidBodyEvents(rigidBodyRefs, mergedOptions, rigidBodyEvents);
1516
+ const contextValue = useMemo(() => {
1517
+ return {
1478
1518
  ref: object,
1479
1519
  api,
1480
- options: props
1481
- }
1520
+ options: mergedOptions
1521
+ };
1522
+ }, [api, mergedOptions]);
1523
+ return /*#__PURE__*/React.createElement(RigidBodyContext.Provider, {
1524
+ value: contextValue
1482
1525
  }, /*#__PURE__*/React.createElement("object3D", {
1483
1526
  ref: object
1484
1527
  }, props.children, childColliderProps.map((colliderProps, index) => /*#__PURE__*/React.createElement(AnyCollider, _extends({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/rapier",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "source": "src/index.ts",
5
5
  "main": "dist/react-three-rapier.cjs.js",
6
6
  "module": "dist/react-three-rapier.esm.js",
package/readme.md CHANGED
@@ -309,6 +309,22 @@ A Collider can be set to be a sensor, which means that it will not generate any
309
309
 
310
310
  WIP
311
311
 
312
+ ## Configuring Time Step Size
313
+
314
+ By default, `<Physics>` will simulate the physics world at a fixed rate of 60 frames per second. This can be changed by setting the `timeStep` prop on `<Physics>`:
315
+
316
+ ```tsx
317
+ <Physics timeStep={1 / 30}>{/* ... */}</Physics>
318
+ ```
319
+
320
+ The `timeStep` prop may also be set to `"vary"`, which will cause the simulation's time step to adjust to every frame's frame delta:
321
+
322
+ ```tsx
323
+ <Physics timeStep="vary">{/* ... */}</Physics>
324
+ ```
325
+
326
+ > **Note** This is useful for games that run at variable frame rates, but may cause instability in the simulation. It also prevents the physics simulation from being fully deterministic. Please use with care!
327
+
312
328
  ---
313
329
 
314
330
  ## Roadmap
@@ -326,6 +342,6 @@ In order, but also not necessarily:
326
342
  - [x] InstancedMesh support
327
343
  - [x] Timestep improvements for determinism
328
344
  - [x] Normalize and improve collision events (add events to single Colliders)
329
- - [ ] Add collision events to InstancedRigidBodies
345
+ - [x] Add collision events to InstancedRigidBodies
330
346
  - [ ] Docs
331
347
  - [ ] CodeSandbox examples