@react-three/rapier 0.6.3 → 0.6.6

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.
@@ -7,4 +7,4 @@ export interface InstancedRigidBodiesProps extends Omit<RigidBodyProps, "positio
7
7
  rotations?: Vector3Array[];
8
8
  scales?: Vector3Array[];
9
9
  }
10
- export declare const InstancedRigidBodies: React.ForwardRefExoticComponent<InstancedRigidBodiesProps & React.RefAttributes<InstancedRigidBodyApi>>;
10
+ export declare const InstancedRigidBodies: React.ForwardRefExoticComponent<Pick<InstancedRigidBodiesProps, "type" | "canSleep" | "linearDamping" | "angularDamping" | "linearVelocity" | "angularVelocity" | "gravityScale" | "ccd" | "colliders" | "friction" | "restitution" | "onSleep" | "onWake" | "lockRotations" | "lockTranslations" | "enabledRotations" | "enabledTranslations" | "children" | "quaternion" | "attach" | "args" | "key" | "onUpdate" | "up" | "scale" | "matrix" | "layers" | "dispose" | "id" | "uuid" | "name" | "parent" | "modelViewMatrix" | "normalMatrix" | "matrixWorld" | "matrixAutoUpdate" | "matrixWorldNeedsUpdate" | "visible" | "castShadow" | "receiveShadow" | "frustumCulled" | "renderOrder" | "animations" | "userData" | "customDepthMaterial" | "customDistanceMaterial" | "isObject3D" | "onBeforeRender" | "onAfterRender" | "applyMatrix4" | "applyQuaternion" | "setRotationFromAxisAngle" | "setRotationFromEuler" | "setRotationFromMatrix" | "setRotationFromQuaternion" | "rotateOnAxis" | "rotateOnWorldAxis" | "rotateX" | "rotateY" | "rotateZ" | "translateOnAxis" | "translateX" | "translateY" | "translateZ" | "localToWorld" | "worldToLocal" | "lookAt" | "add" | "remove" | "removeFromParent" | "clear" | "getObjectById" | "getObjectByName" | "getObjectByProperty" | "getWorldPosition" | "getWorldQuaternion" | "getWorldScale" | "getWorldDirection" | "raycast" | "traverse" | "traverseVisible" | "traverseAncestors" | "updateMatrix" | "updateMatrixWorld" | "updateWorldMatrix" | "toJSON" | "clone" | "copy" | "addEventListener" | "hasEventListener" | "removeEventListener" | "dispatchEvent" | "onClick" | "onContextMenu" | "onDoubleClick" | "onPointerUp" | "onPointerDown" | "onPointerOver" | "onPointerOut" | "onPointerEnter" | "onPointerLeave" | "onPointerMove" | "onPointerMissed" | "onPointerCancel" | "onWheel" | "positions" | "rotations" | "scales"> & React.RefAttributes<InstancedRigidBodyApi>>;
@@ -19,6 +19,7 @@ export interface RapierContext {
19
19
  colliders: RigidBodyAutoCollider;
20
20
  };
21
21
  rigidBodyEvents: EventMap;
22
+ isPaused: boolean;
22
23
  }
23
24
  export declare const RapierContext: React.Context<RapierContext | undefined>;
24
25
  declare type EventMap = Map<ColliderHandle | RigidBodyHandle, {
@@ -51,20 +52,21 @@ interface RapierWorldProps {
51
52
  * Setting this to a number (eg. 1/60) will run the
52
53
  * simulation at that framerate.
53
54
  *
54
- * "vary" will run the simulation at a delta-value based
55
- * on the users current framerate. This ensures simulations
56
- * run at the same percieved speed at all framerates, but
57
- * can also lead to instability.
55
+ * @defaultValue 1/60
56
+ */
57
+ timeStep?: number;
58
+ /**
59
+ * Maximum number of fixed steps to take per function call.
58
60
  *
59
- * @defaultValue "vary"
61
+ * @defaultValue 10
60
62
  */
61
- timeStep?: number | "vary";
63
+ maxSubSteps?: number;
62
64
  /**
63
65
  * Pause the physics simulation
64
66
  *
65
67
  * @defaultValue false
66
68
  */
67
- paused: boolean;
69
+ paused?: boolean;
68
70
  }
69
71
  export declare const Physics: FC<RapierWorldProps>;
70
72
  export {};
@@ -1,6 +1,7 @@
1
1
  import React, { MutableRefObject, RefObject } from "react";
2
2
  import { ReactNode } from "react";
3
3
  import { Object3D } from "three";
4
+ import { Object3DProps } from "@react-three/fiber";
4
5
  import { InstancedRigidBodyApi } from "./api";
5
6
  import { InstancedRigidBodiesProps } from "./InstancedRigidBodies";
6
7
  import { RigidBodyApi, UseRigidBodyOptions } from "./types";
@@ -16,7 +17,7 @@ export declare const useRigidBodyContext: () => {
16
17
  hasCollisionEvents: boolean;
17
18
  options: UseRigidBodyOptions | InstancedRigidBodiesProps;
18
19
  };
19
- export interface RigidBodyProps extends UseRigidBodyOptions {
20
+ export interface RigidBodyProps extends UseRigidBodyOptions, Omit<Object3DProps, 'type' | 'position' | 'rotation'> {
20
21
  children?: ReactNode;
21
22
  }
22
- export declare const RigidBody: React.ForwardRefExoticComponent<RigidBodyProps & React.RefAttributes<import("./api").RigidBodyApi>>;
23
+ export declare const RigidBody: React.ForwardRefExoticComponent<Pick<RigidBodyProps, "type" | "canSleep" | "linearDamping" | "angularDamping" | "linearVelocity" | "angularVelocity" | "gravityScale" | "ccd" | "position" | "rotation" | "colliders" | "friction" | "restitution" | "onCollisionEnter" | "onCollisionExit" | "onSleep" | "onWake" | "lockRotations" | "lockTranslations" | "enabledRotations" | "enabledTranslations" | "children" | "quaternion" | "attach" | "args" | "key" | "onUpdate" | "up" | "scale" | "matrix" | "layers" | "dispose" | "id" | "uuid" | "name" | "parent" | "modelViewMatrix" | "normalMatrix" | "matrixWorld" | "matrixAutoUpdate" | "matrixWorldNeedsUpdate" | "visible" | "castShadow" | "receiveShadow" | "frustumCulled" | "renderOrder" | "animations" | "userData" | "customDepthMaterial" | "customDistanceMaterial" | "isObject3D" | "onBeforeRender" | "onAfterRender" | "applyMatrix4" | "applyQuaternion" | "setRotationFromAxisAngle" | "setRotationFromEuler" | "setRotationFromMatrix" | "setRotationFromQuaternion" | "rotateOnAxis" | "rotateOnWorldAxis" | "rotateX" | "rotateY" | "rotateZ" | "translateOnAxis" | "translateX" | "translateY" | "translateZ" | "localToWorld" | "worldToLocal" | "lookAt" | "add" | "remove" | "removeFromParent" | "clear" | "getObjectById" | "getObjectByName" | "getObjectByProperty" | "getWorldPosition" | "getWorldQuaternion" | "getWorldScale" | "getWorldDirection" | "raycast" | "traverse" | "traverseVisible" | "traverseAncestors" | "updateMatrix" | "updateMatrixWorld" | "updateWorldMatrix" | "toJSON" | "clone" | "copy" | "addEventListener" | "hasEventListener" | "removeEventListener" | "dispatchEvent" | "onClick" | "onContextMenu" | "onDoubleClick" | "onPointerUp" | "onPointerDown" | "onPointerOver" | "onPointerOut" | "onPointerEnter" | "onPointerLeave" | "onPointerMove" | "onPointerMissed" | "onPointerCancel" | "onWheel"> & React.RefAttributes<import("./api").RigidBodyApi>>;
@@ -7,6 +7,7 @@ var React = require('react');
7
7
  var useAsset = require('use-asset');
8
8
  var fiber = require('@react-three/fiber');
9
9
  var three = require('three');
10
+ var BufferGeometryUtils = require('three/examples/jsm/utils/BufferGeometryUtils');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
13
 
@@ -244,7 +245,9 @@ const colliderDescFromGeometry = (geometry, colliders, scale, hasCollisionEvents
244
245
  {
245
246
  var _g$index;
246
247
 
247
- const _g = geometry.clone().scale(scale.x, scale.y, scale.z);
248
+ const clonedGeometry = geometry.index ? geometry.clone() : BufferGeometryUtils.mergeVertices(geometry);
249
+
250
+ const _g = clonedGeometry.scale(scale.x, scale.y, scale.z);
248
251
 
249
252
  desc = rapier3dCompat.ColliderDesc.trimesh(_g.attributes.position.array, (_g$index = _g.index) === null || _g$index === void 0 ? void 0 : _g$index.array);
250
253
  }
@@ -469,10 +472,15 @@ const Physics = ({
469
472
  colliders: _colliders = "cuboid",
470
473
  gravity: _gravity = [0, -9.81, 0],
471
474
  children,
472
- timeStep: _timeStep = "vary",
475
+ timeStep: _timeStep = 1 / 60,
476
+ maxSubSteps: _maxSubSteps = 10,
473
477
  paused: _paused = false
474
478
  }) => {
475
479
  const rapier = useAsset.useAsset(importRapier);
480
+ const [isPaused, setIsPaused] = React.useState(_paused);
481
+ React.useEffect(() => {
482
+ setIsPaused(_paused);
483
+ }, [_paused]);
476
484
  const worldRef = React.useRef();
477
485
  const getWorldRef = React.useRef(() => {
478
486
  if (!worldRef.current) {
@@ -492,7 +500,6 @@ const Physics = ({
492
500
  return () => {
493
501
  if (world) {
494
502
  world.free();
495
- worldRef.current = undefined;
496
503
  }
497
504
  };
498
505
  }, []); // Update gravity
@@ -504,22 +511,46 @@ const Physics = ({
504
511
  world.gravity = vectorArrayToVector3(_gravity);
505
512
  }
506
513
  }, [_gravity]);
507
- const time = React.useRef(performance.now());
508
- fiber.useFrame(context => {
514
+ const [steppingState] = React.useState({
515
+ time: 0,
516
+ lastTime: 0,
517
+ accumulator: 0
518
+ });
519
+ fiber.useFrame((_, delta) => {
509
520
  const world = worldRef.current;
510
- if (!world) return; // Set timestep to current delta, to allow for variable frame rates
511
- // We cap the delta at 100, so that the physics simulation doesn't get wild
512
-
513
- const now = performance.now();
514
- const delta = Math.min(100, now - time.current);
515
-
516
- if (_timeStep === "vary") {
517
- world.timestep = delta / 1000;
518
- } else {
519
- world.timestep = _timeStep;
521
+ if (!world) return;
522
+ world.timestep = _timeStep;
523
+ /**
524
+ * Fixed timeStep simulation progression
525
+ * @see https://gafferongames.com/post/fix_your_timestep/
526
+ */
527
+
528
+ let previousTranslations = {}; // don't step time forwards if paused
529
+
530
+ const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
531
+ const timeStepMs = _timeStep * 1000;
532
+ const timeSinceLast = nowTime - steppingState.lastTime;
533
+ steppingState.lastTime = nowTime;
534
+ steppingState.accumulator += timeSinceLast;
535
+
536
+ if (!_paused) {
537
+ let subSteps = 0;
538
+
539
+ while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
540
+ // Collect previous state
541
+ world.bodies.forEach(b => {
542
+ previousTranslations[b.handle] = {
543
+ rotation: rapierQuaternionToQuaternion(b.rotation()).normalize(),
544
+ translation: rapierVector3ToVector3(b.translation())
545
+ };
546
+ });
547
+ world.step(eventQueue);
548
+ subSteps++;
549
+ steppingState.accumulator -= timeStepMs;
550
+ }
520
551
  }
521
552
 
522
- if (!_paused) world.step(eventQueue); // Update meshes
553
+ const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
523
554
 
524
555
  rigidBodyStates.forEach((state, handle) => {
525
556
  const rigidBody = world.getRigidBody(handle);
@@ -545,7 +576,12 @@ const Physics = ({
545
576
  return;
546
577
  }
547
578
 
548
- state.setMatrix(_matrix4.compose(rapierVector3ToVector3(rigidBody.translation()), rapierQuaternionToQuaternion(rigidBody.rotation()), state.worldScale).premultiply(state.invertedMatrixWorld));
579
+ let oldState = previousTranslations[rigidBody.handle];
580
+ let newTranslation = rapierVector3ToVector3(rigidBody.translation());
581
+ let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
582
+ let interpolatedTranslation = oldState ? oldState.translation.lerp(newTranslation, 1) : newTranslation;
583
+ let interpolatedRotation = oldState ? oldState.rotation.slerp(newRotation, interpolationAlpha) : newRotation;
584
+ state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
549
585
 
550
586
  if (state.mesh instanceof three.InstancedMesh) {
551
587
  state.mesh.instanceMatrix.needsUpdate = true;
@@ -595,7 +631,6 @@ const Physics = ({
595
631
  });
596
632
  }
597
633
  });
598
- time.current = now;
599
634
  });
600
635
  const api = React.useMemo(() => createWorldApi(getWorldRef), []);
601
636
  const context = React.useMemo(() => ({
@@ -607,13 +642,32 @@ const Physics = ({
607
642
  },
608
643
  colliderMeshes,
609
644
  rigidBodyStates,
610
- rigidBodyEvents
611
- }), []);
645
+ rigidBodyEvents,
646
+ isPaused
647
+ }), [isPaused]);
612
648
  return /*#__PURE__*/React__default["default"].createElement(RapierContext.Provider, {
613
649
  value: context
614
650
  }, children);
615
651
  };
616
652
 
653
+ function _extends() {
654
+ _extends = Object.assign || function (target) {
655
+ for (var i = 1; i < arguments.length; i++) {
656
+ var source = arguments[i];
657
+
658
+ for (var key in source) {
659
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
660
+ target[key] = source[key];
661
+ }
662
+ }
663
+ }
664
+
665
+ return target;
666
+ };
667
+
668
+ return _extends.apply(this, arguments);
669
+ }
670
+
617
671
  function _objectWithoutPropertiesLoose(source, excluded) {
618
672
  if (source == null) return {};
619
673
  var target = {};
@@ -880,7 +934,8 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis]) => {
880
934
  return useImpulseJoint(body1, body2, rapier.JointData.prismatic(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis)));
881
935
  };
882
936
 
883
- const _excluded$1 = ["children"];
937
+ const _excluded$1 = ["children"],
938
+ _excluded2 = ["type", "position", "rotation"];
884
939
  const RigidBodyContext = /*#__PURE__*/React.createContext(undefined);
885
940
  const useRigidBodyContext = () => React.useContext(RigidBodyContext); // RigidBody
886
941
 
@@ -891,6 +946,9 @@ const RigidBody = /*#__PURE__*/React.forwardRef((_ref, ref) => {
891
946
  props = _objectWithoutProperties(_ref, _excluded$1);
892
947
 
893
948
  const [object, api] = useRigidBody(props);
949
+
950
+ const objectProps = _objectWithoutProperties(props, _excluded2);
951
+
894
952
  React.useImperativeHandle(ref, () => api);
895
953
  return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
896
954
  value: {
@@ -899,9 +957,9 @@ const RigidBody = /*#__PURE__*/React.forwardRef((_ref, ref) => {
899
957
  hasCollisionEvents: !!(props.onCollisionEnter || props.onCollisionExit),
900
958
  options: props
901
959
  }
902
- }, /*#__PURE__*/React__default["default"].createElement("object3D", {
960
+ }, /*#__PURE__*/React__default["default"].createElement("object3D", _extends({
903
961
  ref: object
904
- }, children));
962
+ }, objectProps), children));
905
963
  });
906
964
 
907
965
  const MeshCollider = ({
@@ -1191,24 +1249,6 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1191
1249
  }, props.children));
1192
1250
  });
1193
1251
 
1194
- function _extends() {
1195
- _extends = Object.assign || function (target) {
1196
- for (var i = 1; i < arguments.length; i++) {
1197
- var source = arguments[i];
1198
-
1199
- for (var key in source) {
1200
- if (Object.prototype.hasOwnProperty.call(source, key)) {
1201
- target[key] = source[key];
1202
- }
1203
- }
1204
- }
1205
-
1206
- return target;
1207
- };
1208
-
1209
- return _extends.apply(this, arguments);
1210
- }
1211
-
1212
1252
  const _excluded = ["children"];
1213
1253
 
1214
1254
  const AnyCollider = _ref => {
@@ -7,6 +7,7 @@ var React = require('react');
7
7
  var useAsset = require('use-asset');
8
8
  var fiber = require('@react-three/fiber');
9
9
  var three = require('three');
10
+ var BufferGeometryUtils = require('three/examples/jsm/utils/BufferGeometryUtils');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
13
 
@@ -244,7 +245,9 @@ const colliderDescFromGeometry = (geometry, colliders, scale, hasCollisionEvents
244
245
  {
245
246
  var _g$index;
246
247
 
247
- const _g = geometry.clone().scale(scale.x, scale.y, scale.z);
248
+ const clonedGeometry = geometry.index ? geometry.clone() : BufferGeometryUtils.mergeVertices(geometry);
249
+
250
+ const _g = clonedGeometry.scale(scale.x, scale.y, scale.z);
248
251
 
249
252
  desc = rapier3dCompat.ColliderDesc.trimesh(_g.attributes.position.array, (_g$index = _g.index) === null || _g$index === void 0 ? void 0 : _g$index.array);
250
253
  }
@@ -469,10 +472,15 @@ const Physics = ({
469
472
  colliders: _colliders = "cuboid",
470
473
  gravity: _gravity = [0, -9.81, 0],
471
474
  children,
472
- timeStep: _timeStep = "vary",
475
+ timeStep: _timeStep = 1 / 60,
476
+ maxSubSteps: _maxSubSteps = 10,
473
477
  paused: _paused = false
474
478
  }) => {
475
479
  const rapier = useAsset.useAsset(importRapier);
480
+ const [isPaused, setIsPaused] = React.useState(_paused);
481
+ React.useEffect(() => {
482
+ setIsPaused(_paused);
483
+ }, [_paused]);
476
484
  const worldRef = React.useRef();
477
485
  const getWorldRef = React.useRef(() => {
478
486
  if (!worldRef.current) {
@@ -492,7 +500,6 @@ const Physics = ({
492
500
  return () => {
493
501
  if (world) {
494
502
  world.free();
495
- worldRef.current = undefined;
496
503
  }
497
504
  };
498
505
  }, []); // Update gravity
@@ -504,22 +511,46 @@ const Physics = ({
504
511
  world.gravity = vectorArrayToVector3(_gravity);
505
512
  }
506
513
  }, [_gravity]);
507
- const time = React.useRef(performance.now());
508
- fiber.useFrame(context => {
514
+ const [steppingState] = React.useState({
515
+ time: 0,
516
+ lastTime: 0,
517
+ accumulator: 0
518
+ });
519
+ fiber.useFrame((_, delta) => {
509
520
  const world = worldRef.current;
510
- if (!world) return; // Set timestep to current delta, to allow for variable frame rates
511
- // We cap the delta at 100, so that the physics simulation doesn't get wild
512
-
513
- const now = performance.now();
514
- const delta = Math.min(100, now - time.current);
515
-
516
- if (_timeStep === "vary") {
517
- world.timestep = delta / 1000;
518
- } else {
519
- world.timestep = _timeStep;
521
+ if (!world) return;
522
+ world.timestep = _timeStep;
523
+ /**
524
+ * Fixed timeStep simulation progression
525
+ * @see https://gafferongames.com/post/fix_your_timestep/
526
+ */
527
+
528
+ let previousTranslations = {}; // don't step time forwards if paused
529
+
530
+ const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
531
+ const timeStepMs = _timeStep * 1000;
532
+ const timeSinceLast = nowTime - steppingState.lastTime;
533
+ steppingState.lastTime = nowTime;
534
+ steppingState.accumulator += timeSinceLast;
535
+
536
+ if (!_paused) {
537
+ let subSteps = 0;
538
+
539
+ while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
540
+ // Collect previous state
541
+ world.bodies.forEach(b => {
542
+ previousTranslations[b.handle] = {
543
+ rotation: rapierQuaternionToQuaternion(b.rotation()).normalize(),
544
+ translation: rapierVector3ToVector3(b.translation())
545
+ };
546
+ });
547
+ world.step(eventQueue);
548
+ subSteps++;
549
+ steppingState.accumulator -= timeStepMs;
550
+ }
520
551
  }
521
552
 
522
- if (!_paused) world.step(eventQueue); // Update meshes
553
+ const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
523
554
 
524
555
  rigidBodyStates.forEach((state, handle) => {
525
556
  const rigidBody = world.getRigidBody(handle);
@@ -545,7 +576,12 @@ const Physics = ({
545
576
  return;
546
577
  }
547
578
 
548
- state.setMatrix(_matrix4.compose(rapierVector3ToVector3(rigidBody.translation()), rapierQuaternionToQuaternion(rigidBody.rotation()), state.worldScale).premultiply(state.invertedMatrixWorld));
579
+ let oldState = previousTranslations[rigidBody.handle];
580
+ let newTranslation = rapierVector3ToVector3(rigidBody.translation());
581
+ let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
582
+ let interpolatedTranslation = oldState ? oldState.translation.lerp(newTranslation, 1) : newTranslation;
583
+ let interpolatedRotation = oldState ? oldState.rotation.slerp(newRotation, interpolationAlpha) : newRotation;
584
+ state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
549
585
 
550
586
  if (state.mesh instanceof three.InstancedMesh) {
551
587
  state.mesh.instanceMatrix.needsUpdate = true;
@@ -595,7 +631,6 @@ const Physics = ({
595
631
  });
596
632
  }
597
633
  });
598
- time.current = now;
599
634
  });
600
635
  const api = React.useMemo(() => createWorldApi(getWorldRef), []);
601
636
  const context = React.useMemo(() => ({
@@ -607,13 +642,32 @@ const Physics = ({
607
642
  },
608
643
  colliderMeshes,
609
644
  rigidBodyStates,
610
- rigidBodyEvents
611
- }), []);
645
+ rigidBodyEvents,
646
+ isPaused
647
+ }), [isPaused]);
612
648
  return /*#__PURE__*/React__default["default"].createElement(RapierContext.Provider, {
613
649
  value: context
614
650
  }, children);
615
651
  };
616
652
 
653
+ function _extends() {
654
+ _extends = Object.assign || function (target) {
655
+ for (var i = 1; i < arguments.length; i++) {
656
+ var source = arguments[i];
657
+
658
+ for (var key in source) {
659
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
660
+ target[key] = source[key];
661
+ }
662
+ }
663
+ }
664
+
665
+ return target;
666
+ };
667
+
668
+ return _extends.apply(this, arguments);
669
+ }
670
+
617
671
  function _objectWithoutPropertiesLoose(source, excluded) {
618
672
  if (source == null) return {};
619
673
  var target = {};
@@ -880,7 +934,8 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis]) => {
880
934
  return useImpulseJoint(body1, body2, rapier.JointData.prismatic(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis)));
881
935
  };
882
936
 
883
- const _excluded$1 = ["children"];
937
+ const _excluded$1 = ["children"],
938
+ _excluded2 = ["type", "position", "rotation"];
884
939
  const RigidBodyContext = /*#__PURE__*/React.createContext(undefined);
885
940
  const useRigidBodyContext = () => React.useContext(RigidBodyContext); // RigidBody
886
941
 
@@ -891,6 +946,9 @@ const RigidBody = /*#__PURE__*/React.forwardRef((_ref, ref) => {
891
946
  props = _objectWithoutProperties(_ref, _excluded$1);
892
947
 
893
948
  const [object, api] = useRigidBody(props);
949
+
950
+ const objectProps = _objectWithoutProperties(props, _excluded2);
951
+
894
952
  React.useImperativeHandle(ref, () => api);
895
953
  return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
896
954
  value: {
@@ -899,9 +957,9 @@ const RigidBody = /*#__PURE__*/React.forwardRef((_ref, ref) => {
899
957
  hasCollisionEvents: !!(props.onCollisionEnter || props.onCollisionExit),
900
958
  options: props
901
959
  }
902
- }, /*#__PURE__*/React__default["default"].createElement("object3D", {
960
+ }, /*#__PURE__*/React__default["default"].createElement("object3D", _extends({
903
961
  ref: object
904
- }, children));
962
+ }, objectProps), children));
905
963
  });
906
964
 
907
965
  const MeshCollider = ({
@@ -1191,24 +1249,6 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1191
1249
  }, props.children));
1192
1250
  });
1193
1251
 
1194
- function _extends() {
1195
- _extends = Object.assign || function (target) {
1196
- for (var i = 1; i < arguments.length; i++) {
1197
- var source = arguments[i];
1198
-
1199
- for (var key in source) {
1200
- if (Object.prototype.hasOwnProperty.call(source, key)) {
1201
- target[key] = source[key];
1202
- }
1203
- }
1204
- }
1205
-
1206
- return target;
1207
- };
1208
-
1209
- return _extends.apply(this, arguments);
1210
- }
1211
-
1212
1252
  const _excluded = ["children"];
1213
1253
 
1214
1254
  const AnyCollider = _ref => {
@@ -1,9 +1,10 @@
1
1
  import { ColliderDesc, ActiveEvents, RigidBodyDesc, CoefficientCombineRule, EventQueue, ShapeType } from '@dimforge/rapier3d-compat';
2
2
  export { CoefficientCombineRule, Collider as RapierCollider, RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat';
3
- import React, { useRef, useState, useEffect, useMemo, createContext, useContext, forwardRef, useImperativeHandle, memo, useLayoutEffect } from 'react';
3
+ import React, { useState, useEffect, useRef, useMemo, createContext, useContext, forwardRef, useImperativeHandle, memo, useLayoutEffect } from 'react';
4
4
  import { useAsset } from 'use-asset';
5
5
  import { useFrame } from '@react-three/fiber';
6
6
  import { Quaternion, Euler, Vector3, Object3D, Matrix4, InstancedMesh, CylinderBufferGeometry, BufferGeometry, BufferAttribute, SphereBufferGeometry, BoxBufferGeometry, DynamicDrawUsage } from 'three';
7
+ import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils';
7
8
 
8
9
  const _quaternion = new Quaternion();
9
10
  const _euler = new Euler();
@@ -219,7 +220,9 @@ const colliderDescFromGeometry = (geometry, colliders, scale, hasCollisionEvents
219
220
  {
220
221
  var _g$index;
221
222
 
222
- const _g = geometry.clone().scale(scale.x, scale.y, scale.z);
223
+ const clonedGeometry = geometry.index ? geometry.clone() : mergeVertices(geometry);
224
+
225
+ const _g = clonedGeometry.scale(scale.x, scale.y, scale.z);
223
226
 
224
227
  desc = ColliderDesc.trimesh(_g.attributes.position.array, (_g$index = _g.index) === null || _g$index === void 0 ? void 0 : _g$index.array);
225
228
  }
@@ -444,10 +447,15 @@ const Physics = ({
444
447
  colliders: _colliders = "cuboid",
445
448
  gravity: _gravity = [0, -9.81, 0],
446
449
  children,
447
- timeStep: _timeStep = "vary",
450
+ timeStep: _timeStep = 1 / 60,
451
+ maxSubSteps: _maxSubSteps = 10,
448
452
  paused: _paused = false
449
453
  }) => {
450
454
  const rapier = useAsset(importRapier);
455
+ const [isPaused, setIsPaused] = useState(_paused);
456
+ useEffect(() => {
457
+ setIsPaused(_paused);
458
+ }, [_paused]);
451
459
  const worldRef = useRef();
452
460
  const getWorldRef = useRef(() => {
453
461
  if (!worldRef.current) {
@@ -467,7 +475,6 @@ const Physics = ({
467
475
  return () => {
468
476
  if (world) {
469
477
  world.free();
470
- worldRef.current = undefined;
471
478
  }
472
479
  };
473
480
  }, []); // Update gravity
@@ -479,22 +486,46 @@ const Physics = ({
479
486
  world.gravity = vectorArrayToVector3(_gravity);
480
487
  }
481
488
  }, [_gravity]);
482
- const time = useRef(performance.now());
483
- useFrame(context => {
489
+ const [steppingState] = useState({
490
+ time: 0,
491
+ lastTime: 0,
492
+ accumulator: 0
493
+ });
494
+ useFrame((_, delta) => {
484
495
  const world = worldRef.current;
485
- if (!world) return; // Set timestep to current delta, to allow for variable frame rates
486
- // We cap the delta at 100, so that the physics simulation doesn't get wild
487
-
488
- const now = performance.now();
489
- const delta = Math.min(100, now - time.current);
490
-
491
- if (_timeStep === "vary") {
492
- world.timestep = delta / 1000;
493
- } else {
494
- world.timestep = _timeStep;
496
+ if (!world) return;
497
+ world.timestep = _timeStep;
498
+ /**
499
+ * Fixed timeStep simulation progression
500
+ * @see https://gafferongames.com/post/fix_your_timestep/
501
+ */
502
+
503
+ let previousTranslations = {}; // don't step time forwards if paused
504
+
505
+ const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
506
+ const timeStepMs = _timeStep * 1000;
507
+ const timeSinceLast = nowTime - steppingState.lastTime;
508
+ steppingState.lastTime = nowTime;
509
+ steppingState.accumulator += timeSinceLast;
510
+
511
+ if (!_paused) {
512
+ let subSteps = 0;
513
+
514
+ while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
515
+ // Collect previous state
516
+ world.bodies.forEach(b => {
517
+ previousTranslations[b.handle] = {
518
+ rotation: rapierQuaternionToQuaternion(b.rotation()).normalize(),
519
+ translation: rapierVector3ToVector3(b.translation())
520
+ };
521
+ });
522
+ world.step(eventQueue);
523
+ subSteps++;
524
+ steppingState.accumulator -= timeStepMs;
525
+ }
495
526
  }
496
527
 
497
- if (!_paused) world.step(eventQueue); // Update meshes
528
+ const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
498
529
 
499
530
  rigidBodyStates.forEach((state, handle) => {
500
531
  const rigidBody = world.getRigidBody(handle);
@@ -520,7 +551,12 @@ const Physics = ({
520
551
  return;
521
552
  }
522
553
 
523
- state.setMatrix(_matrix4.compose(rapierVector3ToVector3(rigidBody.translation()), rapierQuaternionToQuaternion(rigidBody.rotation()), state.worldScale).premultiply(state.invertedMatrixWorld));
554
+ let oldState = previousTranslations[rigidBody.handle];
555
+ let newTranslation = rapierVector3ToVector3(rigidBody.translation());
556
+ let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
557
+ let interpolatedTranslation = oldState ? oldState.translation.lerp(newTranslation, 1) : newTranslation;
558
+ let interpolatedRotation = oldState ? oldState.rotation.slerp(newRotation, interpolationAlpha) : newRotation;
559
+ state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
524
560
 
525
561
  if (state.mesh instanceof InstancedMesh) {
526
562
  state.mesh.instanceMatrix.needsUpdate = true;
@@ -570,7 +606,6 @@ const Physics = ({
570
606
  });
571
607
  }
572
608
  });
573
- time.current = now;
574
609
  });
575
610
  const api = useMemo(() => createWorldApi(getWorldRef), []);
576
611
  const context = useMemo(() => ({
@@ -582,13 +617,32 @@ const Physics = ({
582
617
  },
583
618
  colliderMeshes,
584
619
  rigidBodyStates,
585
- rigidBodyEvents
586
- }), []);
620
+ rigidBodyEvents,
621
+ isPaused
622
+ }), [isPaused]);
587
623
  return /*#__PURE__*/React.createElement(RapierContext.Provider, {
588
624
  value: context
589
625
  }, children);
590
626
  };
591
627
 
628
+ function _extends() {
629
+ _extends = Object.assign || function (target) {
630
+ for (var i = 1; i < arguments.length; i++) {
631
+ var source = arguments[i];
632
+
633
+ for (var key in source) {
634
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
635
+ target[key] = source[key];
636
+ }
637
+ }
638
+ }
639
+
640
+ return target;
641
+ };
642
+
643
+ return _extends.apply(this, arguments);
644
+ }
645
+
592
646
  function _objectWithoutPropertiesLoose(source, excluded) {
593
647
  if (source == null) return {};
594
648
  var target = {};
@@ -855,7 +909,8 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis]) => {
855
909
  return useImpulseJoint(body1, body2, rapier.JointData.prismatic(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis)));
856
910
  };
857
911
 
858
- const _excluded$1 = ["children"];
912
+ const _excluded$1 = ["children"],
913
+ _excluded2 = ["type", "position", "rotation"];
859
914
  const RigidBodyContext = /*#__PURE__*/createContext(undefined);
860
915
  const useRigidBodyContext = () => useContext(RigidBodyContext); // RigidBody
861
916
 
@@ -866,6 +921,9 @@ const RigidBody = /*#__PURE__*/forwardRef((_ref, ref) => {
866
921
  props = _objectWithoutProperties(_ref, _excluded$1);
867
922
 
868
923
  const [object, api] = useRigidBody(props);
924
+
925
+ const objectProps = _objectWithoutProperties(props, _excluded2);
926
+
869
927
  useImperativeHandle(ref, () => api);
870
928
  return /*#__PURE__*/React.createElement(RigidBodyContext.Provider, {
871
929
  value: {
@@ -874,9 +932,9 @@ const RigidBody = /*#__PURE__*/forwardRef((_ref, ref) => {
874
932
  hasCollisionEvents: !!(props.onCollisionEnter || props.onCollisionExit),
875
933
  options: props
876
934
  }
877
- }, /*#__PURE__*/React.createElement("object3D", {
935
+ }, /*#__PURE__*/React.createElement("object3D", _extends({
878
936
  ref: object
879
- }, children));
937
+ }, objectProps), children));
880
938
  });
881
939
 
882
940
  const MeshCollider = ({
@@ -1166,24 +1224,6 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1166
1224
  }, props.children));
1167
1225
  });
1168
1226
 
1169
- function _extends() {
1170
- _extends = Object.assign || function (target) {
1171
- for (var i = 1; i < arguments.length; i++) {
1172
- var source = arguments[i];
1173
-
1174
- for (var key in source) {
1175
- if (Object.prototype.hasOwnProperty.call(source, key)) {
1176
- target[key] = source[key];
1177
- }
1178
- }
1179
- }
1180
-
1181
- return target;
1182
- };
1183
-
1184
- return _extends.apply(this, arguments);
1185
- }
1186
-
1187
1227
  const _excluded = ["children"];
1188
1228
 
1189
1229
  const AnyCollider = _ref => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/rapier",
3
- "version": "0.6.3",
3
+ "version": "0.6.6",
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
@@ -251,6 +251,7 @@ In order, but also not necessarily:
251
251
  - [x] Collision events
252
252
  - [x] Colliders outside RigidBodies
253
253
  - [x] InstancedMesh support
254
+ - [x] Timestep improvements for determinism
254
255
  - [ ] Normalize and improve collision events (add events to single Colliders, InstancedRigidBodies, etc)
255
256
  - [ ] Docs
256
257
  - [ ] CodeSandbox examples