@react-three/rapier 0.6.2 → 0.6.5

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.
@@ -2,10 +2,9 @@ import React from "react";
2
2
  import { InstancedRigidBodyApi } from "./api";
3
3
  import { RigidBodyProps } from "./RigidBody";
4
4
  import { Vector3Array } from "./types";
5
- interface InstancedRigidBodiesProps extends Omit<RigidBodyProps, "position" | "rotation" | "onCollisionEnter" | "onCollisionExit"> {
5
+ export interface InstancedRigidBodiesProps extends Omit<RigidBodyProps, "position" | "rotation" | "onCollisionEnter" | "onCollisionExit"> {
6
6
  positions?: Vector3Array[];
7
7
  rotations?: Vector3Array[];
8
8
  scales?: Vector3Array[];
9
9
  }
10
10
  export declare const InstancedRigidBodies: React.ForwardRefExoticComponent<InstancedRigidBodiesProps & React.RefAttributes<InstancedRigidBodyApi>>;
11
- export {};
@@ -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,14 +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.
60
+ *
61
+ * @defaultValue 10
62
+ */
63
+ maxSubSteps?: number;
64
+ /**
65
+ * Pause the physics simulation
58
66
  *
59
- * @defaultValue "vary"
67
+ * @defaultValue false
60
68
  */
61
- timeStep?: number | "vary";
69
+ paused?: boolean;
62
70
  }
63
71
  export declare const Physics: FC<RapierWorldProps>;
64
72
  export {};
@@ -1,12 +1,20 @@
1
- import React, { MutableRefObject } from "react";
1
+ import React, { MutableRefObject, RefObject } from "react";
2
2
  import { ReactNode } from "react";
3
3
  import { Object3D } from "three";
4
+ import { InstancedRigidBodyApi } from "./api";
5
+ import { InstancedRigidBodiesProps } from "./InstancedRigidBodies";
4
6
  import { RigidBodyApi, UseRigidBodyOptions } from "./types";
7
+ export declare const RigidBodyContext: React.Context<{
8
+ ref: RefObject<Object3D> | MutableRefObject<Object3D>;
9
+ api: RigidBodyApi | InstancedRigidBodyApi;
10
+ hasCollisionEvents: boolean;
11
+ options: UseRigidBodyOptions | InstancedRigidBodiesProps;
12
+ }>;
5
13
  export declare const useRigidBodyContext: () => {
6
- ref: MutableRefObject<Object3D>;
7
- api: RigidBodyApi;
14
+ ref: RefObject<Object3D> | MutableRefObject<Object3D>;
15
+ api: RigidBodyApi | InstancedRigidBodyApi;
8
16
  hasCollisionEvents: boolean;
9
- options: UseRigidBodyOptions;
17
+ options: UseRigidBodyOptions | InstancedRigidBodiesProps;
10
18
  };
11
19
  export interface RigidBodyProps extends UseRigidBodyOptions {
12
20
  children?: ReactNode;
@@ -75,6 +75,22 @@ export interface RigidBodyApi {
75
75
  * Sets the angular velocity of this rigid-body.
76
76
  */
77
77
  setAngvel(velocity: Vector3Object): void;
78
+ /**
79
+ * The linear damping of this rigid-body.
80
+ */
81
+ linearDamping(): number;
82
+ /**
83
+ * Sets the linear damping factor applied to this rigid-body.
84
+ */
85
+ setLinearDamping(factor: number): void;
86
+ /**
87
+ * The angular damping of this rigid-body.
88
+ */
89
+ angularDamping(): number;
90
+ /**
91
+ * Sets the anugular damping factor applied to this rigid-body.
92
+ */
93
+ setAngularDamping(factor: number): void;
78
94
  /**
79
95
  * If this rigid body is kinematic, sets its future rotation after the next timestep integration.
80
96
  *
@@ -1,8 +1,8 @@
1
1
  import { MutableRefObject } from "react";
2
- import { CoefficientCombineRule, RigidBody as RapierRigidBody, Collider as RapierCollider, TempContactManifold } from "@dimforge/rapier3d-compat";
2
+ import { CoefficientCombineRule, Collider as RapierCollider, RigidBody as RapierRigidBody, TempContactManifold } from "@dimforge/rapier3d-compat";
3
3
  import { createColliderApi, createJointApi, createRigidBodyApi, createWorldApi } from "./api";
4
- export { RapierRigidBody, RapierCollider };
5
4
  export { CoefficientCombineRule as CoefficientCombineRule } from "@dimforge/rapier3d-compat";
5
+ export { RapierRigidBody, RapierCollider };
6
6
  export declare type RefGetter<T> = MutableRefObject<() => T | undefined>;
7
7
  export declare type RigidBodyAutoCollider = "ball" | "cuboid" | "hull" | "trimesh" | false;
8
8
  export interface UseRigidBodyAPI {
@@ -126,6 +126,10 @@ export interface UseRigidBodyOptions {
126
126
  * default: true
127
127
  */
128
128
  canSleep?: boolean;
129
+ /** The linear damping coefficient of this rigid-body.*/
130
+ linearDamping?: number;
131
+ /** The angular damping coefficient of this rigid-body.*/
132
+ angularDamping?: number;
129
133
  /** The linear velocity of this body.
130
134
  * default: zero velocity
131
135
  */
@@ -1,4 +1,4 @@
1
- import { Collider, ColliderDesc, RigidBody, RigidBodyDesc, Vector3 as RapierVector3, Quaternion as RapierQuaternion } from "@dimforge/rapier3d-compat";
1
+ import { Collider, ColliderDesc, Quaternion as RapierQuaternion, RigidBody, RigidBodyDesc, Vector3 as RapierVector3 } from "@dimforge/rapier3d-compat";
2
2
  import { BufferGeometry, Matrix4, Object3D, Quaternion, Vector3 } from "three";
3
3
  import { RigidBodyApi, RigidBodyAutoCollider, RigidBodyShape, RigidBodyTypeString, UseColliderOptions, UseRigidBodyOptions, Vector3Array, WorldApi } from "./types";
4
4
  export declare const vectorArrayToVector3: (arr: Vector3Array) => Vector3;
@@ -26,7 +26,16 @@ interface CreateColliderFromOptions {
26
26
  }): Collider;
27
27
  }
28
28
  export declare const createColliderFromOptions: CreateColliderFromOptions;
29
- export declare const createCollidersFromChildren: (object: Object3D, rigidBody: RigidBodyApi | RigidBody, options: UseRigidBodyOptions, world: WorldApi, ignoreMeshColliders?: boolean) => Collider[];
29
+ interface CreateCollidersFromChildren {
30
+ (options: {
31
+ object: Object3D;
32
+ rigidBody?: Pick<RigidBodyApi | RigidBody, "handle">;
33
+ options: UseRigidBodyOptions;
34
+ world: WorldApi;
35
+ ignoreMeshColliders: boolean;
36
+ }): Collider[];
37
+ }
38
+ export declare const createCollidersFromChildren: CreateCollidersFromChildren;
30
39
  export declare const colliderDescFromGeometry: (geometry: BufferGeometry, colliders: RigidBodyAutoCollider, scale: Vector3, hasCollisionEvents: boolean) => ColliderDesc;
31
40
  export declare const scaleVertices: (vertices: ArrayLike<number>, scale: Vector3) => number[];
32
41
  export declare const rigidBodyDescFromOptions: (options: UseRigidBodyOptions) => RigidBodyDesc;
@@ -154,13 +154,19 @@ const isChildOfMeshCollider = child => {
154
154
  return flag;
155
155
  };
156
156
 
157
- const createCollidersFromChildren = (object, rigidBody, options, world, ignoreMeshColliders = true) => {
157
+ const createCollidersFromChildren = ({
158
+ object,
159
+ rigidBody,
160
+ options,
161
+ world,
162
+ ignoreMeshColliders: _ignoreMeshColliders = true
163
+ }) => {
158
164
  const hasCollisionEvents = !!(options.onCollisionEnter || options.onCollisionExit);
159
165
  const colliders = [];
160
166
  new three.Vector3();
161
- object.traverse(child => {
167
+ object.traverseVisible(child => {
162
168
  if ("isMesh" in child) {
163
- if (ignoreMeshColliders && isChildOfMeshCollider(child)) return;
169
+ if (_ignoreMeshColliders && isChildOfMeshCollider(child)) return;
164
170
  const {
165
171
  geometry
166
172
  } = child;
@@ -201,7 +207,7 @@ const createCollidersFromChildren = (object, rigidBody, options, world, ignoreMe
201
207
  z: rz,
202
208
  w: rw
203
209
  });
204
- const actualRigidBody = world.getRigidBody(rigidBody === null || rigidBody === void 0 ? void 0 : rigidBody.handle);
210
+ const actualRigidBody = rigidBody ? world.getRigidBody(rigidBody.handle) : undefined;
205
211
  const collider = world.createCollider(desc, actualRigidBody);
206
212
  colliders.push(collider);
207
213
  }
@@ -267,11 +273,13 @@ const scaleVertices = (vertices, scale) => {
267
273
  return scaledVerts;
268
274
  };
269
275
  const rigidBodyDescFromOptions = options => {
270
- var _options$linearVeloci, _options$angularVeloc, _options$gravityScale, _options$canSleep, _options$ccd, _options$enabledRotat, _options$enabledTrans;
276
+ var _options$linearVeloci, _options$angularVeloc, _options$angularDampi, _options$linearDampin, _options$gravityScale, _options$canSleep, _options$ccd, _options$enabledRotat, _options$enabledTrans;
271
277
 
272
278
  const type = rigidBodyTypeFromString((options === null || options === void 0 ? void 0 : options.type) || "dynamic");
273
279
  const [lvx, lvy, lvz] = (_options$linearVeloci = options === null || options === void 0 ? void 0 : options.linearVelocity) !== null && _options$linearVeloci !== void 0 ? _options$linearVeloci : [0, 0, 0];
274
280
  const [avx, avy, avz] = (_options$angularVeloc = options === null || options === void 0 ? void 0 : options.angularVelocity) !== null && _options$angularVeloc !== void 0 ? _options$angularVeloc : [0, 0, 0];
281
+ const angularDamping = (_options$angularDampi = options === null || options === void 0 ? void 0 : options.angularDamping) !== null && _options$angularDampi !== void 0 ? _options$angularDampi : 0;
282
+ const linearDamping = (_options$linearDampin = options === null || options === void 0 ? void 0 : options.linearDamping) !== null && _options$linearDampin !== void 0 ? _options$linearDampin : 0;
275
283
  const gravityScale = (_options$gravityScale = options === null || options === void 0 ? void 0 : options.gravityScale) !== null && _options$gravityScale !== void 0 ? _options$gravityScale : 1;
276
284
  const canSleep = (_options$canSleep = options === null || options === void 0 ? void 0 : options.canSleep) !== null && _options$canSleep !== void 0 ? _options$canSleep : true;
277
285
  const ccdEnabled = (_options$ccd = options === null || options === void 0 ? void 0 : options.ccd) !== null && _options$ccd !== void 0 ? _options$ccd : false;
@@ -281,7 +289,7 @@ const rigidBodyDescFromOptions = options => {
281
289
  x: avx,
282
290
  y: avy,
283
291
  z: avz
284
- }).setGravityScale(gravityScale).setCanSleep(canSleep).setCcdEnabled(ccdEnabled).enabledRotations(erx, ery, erz).enabledTranslations(etx, ety, etz);
292
+ }).setLinearDamping(linearDamping).setAngularDamping(angularDamping).setGravityScale(gravityScale).setCanSleep(canSleep).setCcdEnabled(ccdEnabled).enabledRotations(erx, ery, erz).enabledTranslations(etx, ety, etz);
285
293
  if (options.lockRotations) desc.lockRotations();
286
294
  if (options.lockTranslations) desc.lockTranslations();
287
295
  return desc;
@@ -366,6 +374,18 @@ const createRigidBodyApi = ref => {
366
374
  },
367
375
 
368
376
  setAngvel: velocity => ref.current().setAngvel(velocity, true),
377
+
378
+ linearDamping() {
379
+ return ref.current().linearDamping();
380
+ },
381
+
382
+ setLinearDamping: factor => ref.current().setLinearDamping(factor),
383
+
384
+ angularDamping() {
385
+ return ref.current().angularDamping();
386
+ },
387
+
388
+ setAngularDamping: factor => ref.current().setAngularDamping(factor),
369
389
  setNextKinematicRotation: ({
370
390
  x,
371
391
  y,
@@ -449,9 +469,15 @@ const Physics = ({
449
469
  colliders: _colliders = "cuboid",
450
470
  gravity: _gravity = [0, -9.81, 0],
451
471
  children,
452
- timeStep: _timeStep = "vary"
472
+ timeStep: _timeStep = 1 / 60,
473
+ maxSubSteps: _maxSubSteps = 10,
474
+ paused: _paused = false
453
475
  }) => {
454
476
  const rapier = useAsset.useAsset(importRapier);
477
+ const [isPaused, setIsPaused] = React.useState(_paused);
478
+ React.useEffect(() => {
479
+ setIsPaused(_paused);
480
+ }, [_paused]);
455
481
  const worldRef = React.useRef();
456
482
  const getWorldRef = React.useRef(() => {
457
483
  if (!worldRef.current) {
@@ -471,7 +497,6 @@ const Physics = ({
471
497
  return () => {
472
498
  if (world) {
473
499
  world.free();
474
- worldRef.current = undefined;
475
500
  }
476
501
  };
477
502
  }, []); // Update gravity
@@ -483,22 +508,46 @@ const Physics = ({
483
508
  world.gravity = vectorArrayToVector3(_gravity);
484
509
  }
485
510
  }, [_gravity]);
486
- const time = React.useRef(performance.now());
487
- fiber.useFrame(context => {
511
+ const [steppingState] = React.useState({
512
+ time: 0,
513
+ lastTime: 0,
514
+ accumulator: 0
515
+ });
516
+ fiber.useFrame((_, delta) => {
488
517
  const world = worldRef.current;
489
- if (!world) return; // Set timestep to current delta, to allow for variable frame rates
490
- // We cap the delta at 100, so that the physics simulation doesn't get wild
491
-
492
- const now = performance.now();
493
- const delta = Math.min(100, now - time.current);
494
-
495
- if (_timeStep === "vary") {
496
- world.timestep = delta / 1000;
497
- } else {
498
- world.timestep = _timeStep;
518
+ if (!world) return;
519
+ world.timestep = _timeStep;
520
+ /**
521
+ * Fixed timeStep simulation progression
522
+ * @see https://gafferongames.com/post/fix_your_timestep/
523
+ */
524
+
525
+ let previousTranslations = {}; // don't step time forwards if paused
526
+
527
+ const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
528
+ const timeStepMs = _timeStep * 1000;
529
+ const timeSinceLast = nowTime - steppingState.lastTime;
530
+ steppingState.lastTime = nowTime;
531
+ steppingState.accumulator += timeSinceLast;
532
+
533
+ if (!_paused) {
534
+ let subSteps = 0;
535
+
536
+ while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
537
+ // Collect previous state
538
+ world.bodies.forEach(b => {
539
+ previousTranslations[b.handle] = {
540
+ rotation: b.rotation(),
541
+ translation: b.translation()
542
+ };
543
+ });
544
+ world.step(eventQueue);
545
+ subSteps++;
546
+ steppingState.accumulator -= timeStepMs;
547
+ }
499
548
  }
500
549
 
501
- world.step(eventQueue); // Update meshes
550
+ const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
502
551
 
503
552
  rigidBodyStates.forEach((state, handle) => {
504
553
  const rigidBody = world.getRigidBody(handle);
@@ -524,7 +573,12 @@ const Physics = ({
524
573
  return;
525
574
  }
526
575
 
527
- state.setMatrix(_matrix4.compose(rapierVector3ToVector3(rigidBody.translation()), rapierQuaternionToQuaternion(rigidBody.rotation()), state.worldScale).premultiply(state.invertedMatrixWorld));
576
+ let oldState = previousTranslations[rigidBody.handle];
577
+ let newTranslation = rapierVector3ToVector3(rigidBody.translation());
578
+ let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
579
+ let interpolatedTranslation = oldState ? rapierVector3ToVector3(oldState.translation).lerp(newTranslation, interpolationAlpha) : newTranslation;
580
+ let interpolatedRotation = oldState ? rapierQuaternionToQuaternion(oldState.rotation).slerp(newRotation, interpolationAlpha) : newRotation;
581
+ state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
528
582
 
529
583
  if (state.mesh instanceof three.InstancedMesh) {
530
584
  state.mesh.instanceMatrix.needsUpdate = true;
@@ -574,7 +628,6 @@ const Physics = ({
574
628
  });
575
629
  }
576
630
  });
577
- time.current = now;
578
631
  });
579
632
  const api = React.useMemo(() => createWorldApi(getWorldRef), []);
580
633
  const context = React.useMemo(() => ({
@@ -586,8 +639,9 @@ const Physics = ({
586
639
  },
587
640
  colliderMeshes,
588
641
  rigidBodyStates,
589
- rigidBodyEvents
590
- }), []);
642
+ rigidBodyEvents,
643
+ isPaused
644
+ }), [isPaused]);
591
645
  return /*#__PURE__*/React__default["default"].createElement(RapierContext.Provider, {
592
646
  value: context
593
647
  }, children);
@@ -730,9 +784,15 @@ const useRigidBody = (options = {}) => {
730
784
  rigidBody.resetForces(false);
731
785
  rigidBody.resetTorques(false);
732
786
  const colliderSetting = (_ref = (_options$colliders = options === null || options === void 0 ? void 0 : options.colliders) !== null && _options$colliders !== void 0 ? _options$colliders : physicsOptions.colliders) !== null && _ref !== void 0 ? _ref : false;
733
- const autoColliders = colliderSetting !== false ? createCollidersFromChildren(ref.current, rigidBody, _objectSpread2(_objectSpread2({}, options), {}, {
734
- colliders: colliderSetting
735
- }), world) : [];
787
+ const autoColliders = colliderSetting !== false ? createCollidersFromChildren({
788
+ object: ref.current,
789
+ rigidBody,
790
+ options: _objectSpread2(_objectSpread2({}, options), {}, {
791
+ colliders: colliderSetting
792
+ }),
793
+ world,
794
+ ignoreMeshColliders: true
795
+ }) : [];
736
796
  rigidBodyStates.set(rigidBody.handle, {
737
797
  mesh: ref.current,
738
798
  invertedMatrixWorld: ref.current.parent.matrixWorld.clone().invert(),
@@ -897,9 +957,18 @@ const MeshCollider = ({
897
957
  var _ref;
898
958
 
899
959
  const colliderSetting = (_ref = type !== null && type !== void 0 ? type : physicsOptions.colliders) !== null && _ref !== void 0 ? _ref : false;
900
- autoColliders = colliderSetting !== false ? createCollidersFromChildren(object.current, api, _objectSpread2(_objectSpread2({}, options), {}, {
901
- colliders: colliderSetting
902
- }), world, false) : [];
960
+
961
+ if ("raw" in api) {
962
+ autoColliders = createCollidersFromChildren({
963
+ object: object.current,
964
+ rigidBody: api,
965
+ options: _objectSpread2(_objectSpread2({}, options), {}, {
966
+ colliders: colliderSetting
967
+ }),
968
+ world,
969
+ ignoreMeshColliders: false
970
+ });
971
+ }
903
972
  }
904
973
 
905
974
  return () => {
@@ -1046,7 +1115,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1046
1115
 
1047
1116
  return instancesRef.current;
1048
1117
  });
1049
- React.useEffect(() => {
1118
+ React.useLayoutEffect(() => {
1050
1119
  const colliders = [];
1051
1120
  const rigidBodies = instancesRefGetter.current();
1052
1121
 
@@ -1072,15 +1141,21 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1072
1141
  scale.multiply(s);
1073
1142
  }
1074
1143
 
1075
- const colliderDesc = colliderDescFromGeometry(mesh.geometry, props.colliders || physicsOptions.colliders, scale, false // Collisions currently not enabled for instances
1076
- );
1077
1144
  const rigidBody = world.createRigidBody(rigidBodyDesc);
1078
1145
  const matrix = new three.Matrix4();
1079
1146
  mesh.getMatrixAt(index, matrix);
1080
1147
  const {
1081
1148
  position,
1082
1149
  rotation
1083
- } = decomposeMatrix4(matrix); // Set positions
1150
+ } = decomposeMatrix4(matrix);
1151
+
1152
+ if (props.colliders !== false) {
1153
+ const colliderDesc = colliderDescFromGeometry(mesh.geometry, props.colliders !== undefined ? props.colliders : physicsOptions.colliders, scale, false // Collisions currently not enabled for instances
1154
+ );
1155
+ const collider = world.createCollider(colliderDesc, rigidBody);
1156
+ colliders.push(collider);
1157
+ } // Set positions
1158
+
1084
1159
 
1085
1160
  if (props.positions && props.positions[index]) {
1086
1161
  rigidBody.setTranslation(vectorArrayToVector3(props.positions[index]), true);
@@ -1096,7 +1171,6 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1096
1171
  rigidBody.setRotation(rotation, true);
1097
1172
  }
1098
1173
 
1099
- const collider = world.createCollider(colliderDesc, rigidBody);
1100
1174
  rigidBodyStates.set(rigidBody.handle, {
1101
1175
  mesh: mesh,
1102
1176
  isSleeping: false,
@@ -1117,7 +1191,6 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1117
1191
  }
1118
1192
 
1119
1193
  });
1120
- colliders.push(collider);
1121
1194
  rigidBodies.push({
1122
1195
  rigidBody,
1123
1196
  api
@@ -1136,10 +1209,19 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1136
1209
  };
1137
1210
  }
1138
1211
  }, []);
1139
- React.useImperativeHandle(ref, () => createInstancedRigidBodiesApi(instancesRefGetter));
1140
- return /*#__PURE__*/React__default["default"].createElement("object3D", {
1212
+ const api = React.useMemo(() => createInstancedRigidBodiesApi(instancesRefGetter), []);
1213
+ React.useImperativeHandle(ref, () => api); // console.log(api);
1214
+
1215
+ return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1216
+ value: {
1217
+ ref: object,
1218
+ api,
1219
+ hasCollisionEvents: false,
1220
+ options: props
1221
+ }
1222
+ }, /*#__PURE__*/React__default["default"].createElement("object3D", {
1141
1223
  ref: object
1142
- }, props.children);
1224
+ }, props.children));
1143
1225
  });
1144
1226
 
1145
1227
  function _extends() {
@@ -1174,18 +1256,40 @@ const AnyCollider = _ref => {
1174
1256
  const rigidBodyContext = useRigidBodyContext();
1175
1257
  const ref = React.useRef(null);
1176
1258
  React.useEffect(() => {
1177
- var _rigidBodyContext$api;
1178
-
1179
1259
  const scale = ref.current.getWorldScale(new three.Vector3());
1180
- const collider = createColliderFromOptions({
1181
- options: props,
1182
- world,
1183
- rigidBody: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : (_rigidBodyContext$api = rigidBodyContext.api) === null || _rigidBodyContext$api === void 0 ? void 0 : _rigidBodyContext$api.raw(),
1184
- scale,
1185
- hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1186
- });
1260
+ const colliders = []; // If this is an InstancedRigidBody api
1261
+
1262
+ if (rigidBodyContext && "at" in rigidBodyContext.api) {
1263
+ rigidBodyContext.api.forEach((body, index) => {
1264
+ var _rigidBodyContext$opt, _rigidBodyContext$opt2;
1265
+
1266
+ let instanceScale = scale.clone();
1267
+
1268
+ if ("scales" in rigidBodyContext.options && rigidBodyContext !== null && rigidBodyContext !== void 0 && (_rigidBodyContext$opt = rigidBodyContext.options) !== null && _rigidBodyContext$opt !== void 0 && (_rigidBodyContext$opt2 = _rigidBodyContext$opt.scales) !== null && _rigidBodyContext$opt2 !== void 0 && _rigidBodyContext$opt2[index]) {
1269
+ instanceScale.multiply(vectorArrayToVector3(rigidBodyContext.options.scales[index]));
1270
+ }
1271
+
1272
+ colliders.push(createColliderFromOptions({
1273
+ options: props,
1274
+ world,
1275
+ rigidBody: body.raw(),
1276
+ scale: instanceScale,
1277
+ hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1278
+ }));
1279
+ });
1280
+ } else {
1281
+ colliders.push(createColliderFromOptions({
1282
+ options: props,
1283
+ world,
1284
+ // Initiate with a rigidbody, or undefined, because colliders can exist without a rigid body
1285
+ rigidBody: rigidBodyContext && "raw" in rigidBodyContext.api ? rigidBodyContext.api.raw() : undefined,
1286
+ scale,
1287
+ hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1288
+ }));
1289
+ }
1290
+
1187
1291
  return () => {
1188
- world.removeCollider(collider);
1292
+ colliders.forEach(collider => world.removeCollider(collider));
1189
1293
  };
1190
1294
  }, []);
1191
1295
  return /*#__PURE__*/React__default["default"].createElement("object3D", {
@@ -154,13 +154,19 @@ const isChildOfMeshCollider = child => {
154
154
  return flag;
155
155
  };
156
156
 
157
- const createCollidersFromChildren = (object, rigidBody, options, world, ignoreMeshColliders = true) => {
157
+ const createCollidersFromChildren = ({
158
+ object,
159
+ rigidBody,
160
+ options,
161
+ world,
162
+ ignoreMeshColliders: _ignoreMeshColliders = true
163
+ }) => {
158
164
  const hasCollisionEvents = !!(options.onCollisionEnter || options.onCollisionExit);
159
165
  const colliders = [];
160
166
  new three.Vector3();
161
- object.traverse(child => {
167
+ object.traverseVisible(child => {
162
168
  if ("isMesh" in child) {
163
- if (ignoreMeshColliders && isChildOfMeshCollider(child)) return;
169
+ if (_ignoreMeshColliders && isChildOfMeshCollider(child)) return;
164
170
  const {
165
171
  geometry
166
172
  } = child;
@@ -201,7 +207,7 @@ const createCollidersFromChildren = (object, rigidBody, options, world, ignoreMe
201
207
  z: rz,
202
208
  w: rw
203
209
  });
204
- const actualRigidBody = world.getRigidBody(rigidBody === null || rigidBody === void 0 ? void 0 : rigidBody.handle);
210
+ const actualRigidBody = rigidBody ? world.getRigidBody(rigidBody.handle) : undefined;
205
211
  const collider = world.createCollider(desc, actualRigidBody);
206
212
  colliders.push(collider);
207
213
  }
@@ -267,11 +273,13 @@ const scaleVertices = (vertices, scale) => {
267
273
  return scaledVerts;
268
274
  };
269
275
  const rigidBodyDescFromOptions = options => {
270
- var _options$linearVeloci, _options$angularVeloc, _options$gravityScale, _options$canSleep, _options$ccd, _options$enabledRotat, _options$enabledTrans;
276
+ var _options$linearVeloci, _options$angularVeloc, _options$angularDampi, _options$linearDampin, _options$gravityScale, _options$canSleep, _options$ccd, _options$enabledRotat, _options$enabledTrans;
271
277
 
272
278
  const type = rigidBodyTypeFromString((options === null || options === void 0 ? void 0 : options.type) || "dynamic");
273
279
  const [lvx, lvy, lvz] = (_options$linearVeloci = options === null || options === void 0 ? void 0 : options.linearVelocity) !== null && _options$linearVeloci !== void 0 ? _options$linearVeloci : [0, 0, 0];
274
280
  const [avx, avy, avz] = (_options$angularVeloc = options === null || options === void 0 ? void 0 : options.angularVelocity) !== null && _options$angularVeloc !== void 0 ? _options$angularVeloc : [0, 0, 0];
281
+ const angularDamping = (_options$angularDampi = options === null || options === void 0 ? void 0 : options.angularDamping) !== null && _options$angularDampi !== void 0 ? _options$angularDampi : 0;
282
+ const linearDamping = (_options$linearDampin = options === null || options === void 0 ? void 0 : options.linearDamping) !== null && _options$linearDampin !== void 0 ? _options$linearDampin : 0;
275
283
  const gravityScale = (_options$gravityScale = options === null || options === void 0 ? void 0 : options.gravityScale) !== null && _options$gravityScale !== void 0 ? _options$gravityScale : 1;
276
284
  const canSleep = (_options$canSleep = options === null || options === void 0 ? void 0 : options.canSleep) !== null && _options$canSleep !== void 0 ? _options$canSleep : true;
277
285
  const ccdEnabled = (_options$ccd = options === null || options === void 0 ? void 0 : options.ccd) !== null && _options$ccd !== void 0 ? _options$ccd : false;
@@ -281,7 +289,7 @@ const rigidBodyDescFromOptions = options => {
281
289
  x: avx,
282
290
  y: avy,
283
291
  z: avz
284
- }).setGravityScale(gravityScale).setCanSleep(canSleep).setCcdEnabled(ccdEnabled).enabledRotations(erx, ery, erz).enabledTranslations(etx, ety, etz);
292
+ }).setLinearDamping(linearDamping).setAngularDamping(angularDamping).setGravityScale(gravityScale).setCanSleep(canSleep).setCcdEnabled(ccdEnabled).enabledRotations(erx, ery, erz).enabledTranslations(etx, ety, etz);
285
293
  if (options.lockRotations) desc.lockRotations();
286
294
  if (options.lockTranslations) desc.lockTranslations();
287
295
  return desc;
@@ -366,6 +374,18 @@ const createRigidBodyApi = ref => {
366
374
  },
367
375
 
368
376
  setAngvel: velocity => ref.current().setAngvel(velocity, true),
377
+
378
+ linearDamping() {
379
+ return ref.current().linearDamping();
380
+ },
381
+
382
+ setLinearDamping: factor => ref.current().setLinearDamping(factor),
383
+
384
+ angularDamping() {
385
+ return ref.current().angularDamping();
386
+ },
387
+
388
+ setAngularDamping: factor => ref.current().setAngularDamping(factor),
369
389
  setNextKinematicRotation: ({
370
390
  x,
371
391
  y,
@@ -449,9 +469,15 @@ const Physics = ({
449
469
  colliders: _colliders = "cuboid",
450
470
  gravity: _gravity = [0, -9.81, 0],
451
471
  children,
452
- timeStep: _timeStep = "vary"
472
+ timeStep: _timeStep = 1 / 60,
473
+ maxSubSteps: _maxSubSteps = 10,
474
+ paused: _paused = false
453
475
  }) => {
454
476
  const rapier = useAsset.useAsset(importRapier);
477
+ const [isPaused, setIsPaused] = React.useState(_paused);
478
+ React.useEffect(() => {
479
+ setIsPaused(_paused);
480
+ }, [_paused]);
455
481
  const worldRef = React.useRef();
456
482
  const getWorldRef = React.useRef(() => {
457
483
  if (!worldRef.current) {
@@ -471,7 +497,6 @@ const Physics = ({
471
497
  return () => {
472
498
  if (world) {
473
499
  world.free();
474
- worldRef.current = undefined;
475
500
  }
476
501
  };
477
502
  }, []); // Update gravity
@@ -483,22 +508,46 @@ const Physics = ({
483
508
  world.gravity = vectorArrayToVector3(_gravity);
484
509
  }
485
510
  }, [_gravity]);
486
- const time = React.useRef(performance.now());
487
- fiber.useFrame(context => {
511
+ const [steppingState] = React.useState({
512
+ time: 0,
513
+ lastTime: 0,
514
+ accumulator: 0
515
+ });
516
+ fiber.useFrame((_, delta) => {
488
517
  const world = worldRef.current;
489
- if (!world) return; // Set timestep to current delta, to allow for variable frame rates
490
- // We cap the delta at 100, so that the physics simulation doesn't get wild
491
-
492
- const now = performance.now();
493
- const delta = Math.min(100, now - time.current);
494
-
495
- if (_timeStep === "vary") {
496
- world.timestep = delta / 1000;
497
- } else {
498
- world.timestep = _timeStep;
518
+ if (!world) return;
519
+ world.timestep = _timeStep;
520
+ /**
521
+ * Fixed timeStep simulation progression
522
+ * @see https://gafferongames.com/post/fix_your_timestep/
523
+ */
524
+
525
+ let previousTranslations = {}; // don't step time forwards if paused
526
+
527
+ const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
528
+ const timeStepMs = _timeStep * 1000;
529
+ const timeSinceLast = nowTime - steppingState.lastTime;
530
+ steppingState.lastTime = nowTime;
531
+ steppingState.accumulator += timeSinceLast;
532
+
533
+ if (!_paused) {
534
+ let subSteps = 0;
535
+
536
+ while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
537
+ // Collect previous state
538
+ world.bodies.forEach(b => {
539
+ previousTranslations[b.handle] = {
540
+ rotation: b.rotation(),
541
+ translation: b.translation()
542
+ };
543
+ });
544
+ world.step(eventQueue);
545
+ subSteps++;
546
+ steppingState.accumulator -= timeStepMs;
547
+ }
499
548
  }
500
549
 
501
- world.step(eventQueue); // Update meshes
550
+ const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
502
551
 
503
552
  rigidBodyStates.forEach((state, handle) => {
504
553
  const rigidBody = world.getRigidBody(handle);
@@ -524,7 +573,12 @@ const Physics = ({
524
573
  return;
525
574
  }
526
575
 
527
- state.setMatrix(_matrix4.compose(rapierVector3ToVector3(rigidBody.translation()), rapierQuaternionToQuaternion(rigidBody.rotation()), state.worldScale).premultiply(state.invertedMatrixWorld));
576
+ let oldState = previousTranslations[rigidBody.handle];
577
+ let newTranslation = rapierVector3ToVector3(rigidBody.translation());
578
+ let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
579
+ let interpolatedTranslation = oldState ? rapierVector3ToVector3(oldState.translation).lerp(newTranslation, interpolationAlpha) : newTranslation;
580
+ let interpolatedRotation = oldState ? rapierQuaternionToQuaternion(oldState.rotation).slerp(newRotation, interpolationAlpha) : newRotation;
581
+ state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
528
582
 
529
583
  if (state.mesh instanceof three.InstancedMesh) {
530
584
  state.mesh.instanceMatrix.needsUpdate = true;
@@ -574,7 +628,6 @@ const Physics = ({
574
628
  });
575
629
  }
576
630
  });
577
- time.current = now;
578
631
  });
579
632
  const api = React.useMemo(() => createWorldApi(getWorldRef), []);
580
633
  const context = React.useMemo(() => ({
@@ -586,8 +639,9 @@ const Physics = ({
586
639
  },
587
640
  colliderMeshes,
588
641
  rigidBodyStates,
589
- rigidBodyEvents
590
- }), []);
642
+ rigidBodyEvents,
643
+ isPaused
644
+ }), [isPaused]);
591
645
  return /*#__PURE__*/React__default["default"].createElement(RapierContext.Provider, {
592
646
  value: context
593
647
  }, children);
@@ -730,9 +784,15 @@ const useRigidBody = (options = {}) => {
730
784
  rigidBody.resetForces(false);
731
785
  rigidBody.resetTorques(false);
732
786
  const colliderSetting = (_ref = (_options$colliders = options === null || options === void 0 ? void 0 : options.colliders) !== null && _options$colliders !== void 0 ? _options$colliders : physicsOptions.colliders) !== null && _ref !== void 0 ? _ref : false;
733
- const autoColliders = colliderSetting !== false ? createCollidersFromChildren(ref.current, rigidBody, _objectSpread2(_objectSpread2({}, options), {}, {
734
- colliders: colliderSetting
735
- }), world) : [];
787
+ const autoColliders = colliderSetting !== false ? createCollidersFromChildren({
788
+ object: ref.current,
789
+ rigidBody,
790
+ options: _objectSpread2(_objectSpread2({}, options), {}, {
791
+ colliders: colliderSetting
792
+ }),
793
+ world,
794
+ ignoreMeshColliders: true
795
+ }) : [];
736
796
  rigidBodyStates.set(rigidBody.handle, {
737
797
  mesh: ref.current,
738
798
  invertedMatrixWorld: ref.current.parent.matrixWorld.clone().invert(),
@@ -897,9 +957,18 @@ const MeshCollider = ({
897
957
  var _ref;
898
958
 
899
959
  const colliderSetting = (_ref = type !== null && type !== void 0 ? type : physicsOptions.colliders) !== null && _ref !== void 0 ? _ref : false;
900
- autoColliders = colliderSetting !== false ? createCollidersFromChildren(object.current, api, _objectSpread2(_objectSpread2({}, options), {}, {
901
- colliders: colliderSetting
902
- }), world, false) : [];
960
+
961
+ if ("raw" in api) {
962
+ autoColliders = createCollidersFromChildren({
963
+ object: object.current,
964
+ rigidBody: api,
965
+ options: _objectSpread2(_objectSpread2({}, options), {}, {
966
+ colliders: colliderSetting
967
+ }),
968
+ world,
969
+ ignoreMeshColliders: false
970
+ });
971
+ }
903
972
  }
904
973
 
905
974
  return () => {
@@ -1046,7 +1115,7 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1046
1115
 
1047
1116
  return instancesRef.current;
1048
1117
  });
1049
- React.useEffect(() => {
1118
+ React.useLayoutEffect(() => {
1050
1119
  const colliders = [];
1051
1120
  const rigidBodies = instancesRefGetter.current();
1052
1121
 
@@ -1072,15 +1141,21 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1072
1141
  scale.multiply(s);
1073
1142
  }
1074
1143
 
1075
- const colliderDesc = colliderDescFromGeometry(mesh.geometry, props.colliders || physicsOptions.colliders, scale, false // Collisions currently not enabled for instances
1076
- );
1077
1144
  const rigidBody = world.createRigidBody(rigidBodyDesc);
1078
1145
  const matrix = new three.Matrix4();
1079
1146
  mesh.getMatrixAt(index, matrix);
1080
1147
  const {
1081
1148
  position,
1082
1149
  rotation
1083
- } = decomposeMatrix4(matrix); // Set positions
1150
+ } = decomposeMatrix4(matrix);
1151
+
1152
+ if (props.colliders !== false) {
1153
+ const colliderDesc = colliderDescFromGeometry(mesh.geometry, props.colliders !== undefined ? props.colliders : physicsOptions.colliders, scale, false // Collisions currently not enabled for instances
1154
+ );
1155
+ const collider = world.createCollider(colliderDesc, rigidBody);
1156
+ colliders.push(collider);
1157
+ } // Set positions
1158
+
1084
1159
 
1085
1160
  if (props.positions && props.positions[index]) {
1086
1161
  rigidBody.setTranslation(vectorArrayToVector3(props.positions[index]), true);
@@ -1096,7 +1171,6 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1096
1171
  rigidBody.setRotation(rotation, true);
1097
1172
  }
1098
1173
 
1099
- const collider = world.createCollider(colliderDesc, rigidBody);
1100
1174
  rigidBodyStates.set(rigidBody.handle, {
1101
1175
  mesh: mesh,
1102
1176
  isSleeping: false,
@@ -1117,7 +1191,6 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1117
1191
  }
1118
1192
 
1119
1193
  });
1120
- colliders.push(collider);
1121
1194
  rigidBodies.push({
1122
1195
  rigidBody,
1123
1196
  api
@@ -1136,10 +1209,19 @@ const InstancedRigidBodies = /*#__PURE__*/React.forwardRef((props, ref) => {
1136
1209
  };
1137
1210
  }
1138
1211
  }, []);
1139
- React.useImperativeHandle(ref, () => createInstancedRigidBodiesApi(instancesRefGetter));
1140
- return /*#__PURE__*/React__default["default"].createElement("object3D", {
1212
+ const api = React.useMemo(() => createInstancedRigidBodiesApi(instancesRefGetter), []);
1213
+ React.useImperativeHandle(ref, () => api); // console.log(api);
1214
+
1215
+ return /*#__PURE__*/React__default["default"].createElement(RigidBodyContext.Provider, {
1216
+ value: {
1217
+ ref: object,
1218
+ api,
1219
+ hasCollisionEvents: false,
1220
+ options: props
1221
+ }
1222
+ }, /*#__PURE__*/React__default["default"].createElement("object3D", {
1141
1223
  ref: object
1142
- }, props.children);
1224
+ }, props.children));
1143
1225
  });
1144
1226
 
1145
1227
  function _extends() {
@@ -1174,18 +1256,40 @@ const AnyCollider = _ref => {
1174
1256
  const rigidBodyContext = useRigidBodyContext();
1175
1257
  const ref = React.useRef(null);
1176
1258
  React.useEffect(() => {
1177
- var _rigidBodyContext$api;
1178
-
1179
1259
  const scale = ref.current.getWorldScale(new three.Vector3());
1180
- const collider = createColliderFromOptions({
1181
- options: props,
1182
- world,
1183
- rigidBody: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : (_rigidBodyContext$api = rigidBodyContext.api) === null || _rigidBodyContext$api === void 0 ? void 0 : _rigidBodyContext$api.raw(),
1184
- scale,
1185
- hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1186
- });
1260
+ const colliders = []; // If this is an InstancedRigidBody api
1261
+
1262
+ if (rigidBodyContext && "at" in rigidBodyContext.api) {
1263
+ rigidBodyContext.api.forEach((body, index) => {
1264
+ var _rigidBodyContext$opt, _rigidBodyContext$opt2;
1265
+
1266
+ let instanceScale = scale.clone();
1267
+
1268
+ if ("scales" in rigidBodyContext.options && rigidBodyContext !== null && rigidBodyContext !== void 0 && (_rigidBodyContext$opt = rigidBodyContext.options) !== null && _rigidBodyContext$opt !== void 0 && (_rigidBodyContext$opt2 = _rigidBodyContext$opt.scales) !== null && _rigidBodyContext$opt2 !== void 0 && _rigidBodyContext$opt2[index]) {
1269
+ instanceScale.multiply(vectorArrayToVector3(rigidBodyContext.options.scales[index]));
1270
+ }
1271
+
1272
+ colliders.push(createColliderFromOptions({
1273
+ options: props,
1274
+ world,
1275
+ rigidBody: body.raw(),
1276
+ scale: instanceScale,
1277
+ hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1278
+ }));
1279
+ });
1280
+ } else {
1281
+ colliders.push(createColliderFromOptions({
1282
+ options: props,
1283
+ world,
1284
+ // Initiate with a rigidbody, or undefined, because colliders can exist without a rigid body
1285
+ rigidBody: rigidBodyContext && "raw" in rigidBodyContext.api ? rigidBodyContext.api.raw() : undefined,
1286
+ scale,
1287
+ hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1288
+ }));
1289
+ }
1290
+
1187
1291
  return () => {
1188
- world.removeCollider(collider);
1292
+ colliders.forEach(collider => world.removeCollider(collider));
1189
1293
  };
1190
1294
  }, []);
1191
1295
  return /*#__PURE__*/React__default["default"].createElement("object3D", {
@@ -1,6 +1,6 @@
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 } 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';
@@ -129,13 +129,19 @@ const isChildOfMeshCollider = child => {
129
129
  return flag;
130
130
  };
131
131
 
132
- const createCollidersFromChildren = (object, rigidBody, options, world, ignoreMeshColliders = true) => {
132
+ const createCollidersFromChildren = ({
133
+ object,
134
+ rigidBody,
135
+ options,
136
+ world,
137
+ ignoreMeshColliders: _ignoreMeshColliders = true
138
+ }) => {
133
139
  const hasCollisionEvents = !!(options.onCollisionEnter || options.onCollisionExit);
134
140
  const colliders = [];
135
141
  new Vector3();
136
- object.traverse(child => {
142
+ object.traverseVisible(child => {
137
143
  if ("isMesh" in child) {
138
- if (ignoreMeshColliders && isChildOfMeshCollider(child)) return;
144
+ if (_ignoreMeshColliders && isChildOfMeshCollider(child)) return;
139
145
  const {
140
146
  geometry
141
147
  } = child;
@@ -176,7 +182,7 @@ const createCollidersFromChildren = (object, rigidBody, options, world, ignoreMe
176
182
  z: rz,
177
183
  w: rw
178
184
  });
179
- const actualRigidBody = world.getRigidBody(rigidBody === null || rigidBody === void 0 ? void 0 : rigidBody.handle);
185
+ const actualRigidBody = rigidBody ? world.getRigidBody(rigidBody.handle) : undefined;
180
186
  const collider = world.createCollider(desc, actualRigidBody);
181
187
  colliders.push(collider);
182
188
  }
@@ -242,11 +248,13 @@ const scaleVertices = (vertices, scale) => {
242
248
  return scaledVerts;
243
249
  };
244
250
  const rigidBodyDescFromOptions = options => {
245
- var _options$linearVeloci, _options$angularVeloc, _options$gravityScale, _options$canSleep, _options$ccd, _options$enabledRotat, _options$enabledTrans;
251
+ var _options$linearVeloci, _options$angularVeloc, _options$angularDampi, _options$linearDampin, _options$gravityScale, _options$canSleep, _options$ccd, _options$enabledRotat, _options$enabledTrans;
246
252
 
247
253
  const type = rigidBodyTypeFromString((options === null || options === void 0 ? void 0 : options.type) || "dynamic");
248
254
  const [lvx, lvy, lvz] = (_options$linearVeloci = options === null || options === void 0 ? void 0 : options.linearVelocity) !== null && _options$linearVeloci !== void 0 ? _options$linearVeloci : [0, 0, 0];
249
255
  const [avx, avy, avz] = (_options$angularVeloc = options === null || options === void 0 ? void 0 : options.angularVelocity) !== null && _options$angularVeloc !== void 0 ? _options$angularVeloc : [0, 0, 0];
256
+ const angularDamping = (_options$angularDampi = options === null || options === void 0 ? void 0 : options.angularDamping) !== null && _options$angularDampi !== void 0 ? _options$angularDampi : 0;
257
+ const linearDamping = (_options$linearDampin = options === null || options === void 0 ? void 0 : options.linearDamping) !== null && _options$linearDampin !== void 0 ? _options$linearDampin : 0;
250
258
  const gravityScale = (_options$gravityScale = options === null || options === void 0 ? void 0 : options.gravityScale) !== null && _options$gravityScale !== void 0 ? _options$gravityScale : 1;
251
259
  const canSleep = (_options$canSleep = options === null || options === void 0 ? void 0 : options.canSleep) !== null && _options$canSleep !== void 0 ? _options$canSleep : true;
252
260
  const ccdEnabled = (_options$ccd = options === null || options === void 0 ? void 0 : options.ccd) !== null && _options$ccd !== void 0 ? _options$ccd : false;
@@ -256,7 +264,7 @@ const rigidBodyDescFromOptions = options => {
256
264
  x: avx,
257
265
  y: avy,
258
266
  z: avz
259
- }).setGravityScale(gravityScale).setCanSleep(canSleep).setCcdEnabled(ccdEnabled).enabledRotations(erx, ery, erz).enabledTranslations(etx, ety, etz);
267
+ }).setLinearDamping(linearDamping).setAngularDamping(angularDamping).setGravityScale(gravityScale).setCanSleep(canSleep).setCcdEnabled(ccdEnabled).enabledRotations(erx, ery, erz).enabledTranslations(etx, ety, etz);
260
268
  if (options.lockRotations) desc.lockRotations();
261
269
  if (options.lockTranslations) desc.lockTranslations();
262
270
  return desc;
@@ -341,6 +349,18 @@ const createRigidBodyApi = ref => {
341
349
  },
342
350
 
343
351
  setAngvel: velocity => ref.current().setAngvel(velocity, true),
352
+
353
+ linearDamping() {
354
+ return ref.current().linearDamping();
355
+ },
356
+
357
+ setLinearDamping: factor => ref.current().setLinearDamping(factor),
358
+
359
+ angularDamping() {
360
+ return ref.current().angularDamping();
361
+ },
362
+
363
+ setAngularDamping: factor => ref.current().setAngularDamping(factor),
344
364
  setNextKinematicRotation: ({
345
365
  x,
346
366
  y,
@@ -424,9 +444,15 @@ const Physics = ({
424
444
  colliders: _colliders = "cuboid",
425
445
  gravity: _gravity = [0, -9.81, 0],
426
446
  children,
427
- timeStep: _timeStep = "vary"
447
+ timeStep: _timeStep = 1 / 60,
448
+ maxSubSteps: _maxSubSteps = 10,
449
+ paused: _paused = false
428
450
  }) => {
429
451
  const rapier = useAsset(importRapier);
452
+ const [isPaused, setIsPaused] = useState(_paused);
453
+ useEffect(() => {
454
+ setIsPaused(_paused);
455
+ }, [_paused]);
430
456
  const worldRef = useRef();
431
457
  const getWorldRef = useRef(() => {
432
458
  if (!worldRef.current) {
@@ -446,7 +472,6 @@ const Physics = ({
446
472
  return () => {
447
473
  if (world) {
448
474
  world.free();
449
- worldRef.current = undefined;
450
475
  }
451
476
  };
452
477
  }, []); // Update gravity
@@ -458,22 +483,46 @@ const Physics = ({
458
483
  world.gravity = vectorArrayToVector3(_gravity);
459
484
  }
460
485
  }, [_gravity]);
461
- const time = useRef(performance.now());
462
- useFrame(context => {
486
+ const [steppingState] = useState({
487
+ time: 0,
488
+ lastTime: 0,
489
+ accumulator: 0
490
+ });
491
+ useFrame((_, delta) => {
463
492
  const world = worldRef.current;
464
- if (!world) return; // Set timestep to current delta, to allow for variable frame rates
465
- // We cap the delta at 100, so that the physics simulation doesn't get wild
466
-
467
- const now = performance.now();
468
- const delta = Math.min(100, now - time.current);
469
-
470
- if (_timeStep === "vary") {
471
- world.timestep = delta / 1000;
472
- } else {
473
- world.timestep = _timeStep;
493
+ if (!world) return;
494
+ world.timestep = _timeStep;
495
+ /**
496
+ * Fixed timeStep simulation progression
497
+ * @see https://gafferongames.com/post/fix_your_timestep/
498
+ */
499
+
500
+ let previousTranslations = {}; // don't step time forwards if paused
501
+
502
+ const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
503
+ const timeStepMs = _timeStep * 1000;
504
+ const timeSinceLast = nowTime - steppingState.lastTime;
505
+ steppingState.lastTime = nowTime;
506
+ steppingState.accumulator += timeSinceLast;
507
+
508
+ if (!_paused) {
509
+ let subSteps = 0;
510
+
511
+ while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
512
+ // Collect previous state
513
+ world.bodies.forEach(b => {
514
+ previousTranslations[b.handle] = {
515
+ rotation: b.rotation(),
516
+ translation: b.translation()
517
+ };
518
+ });
519
+ world.step(eventQueue);
520
+ subSteps++;
521
+ steppingState.accumulator -= timeStepMs;
522
+ }
474
523
  }
475
524
 
476
- world.step(eventQueue); // Update meshes
525
+ const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
477
526
 
478
527
  rigidBodyStates.forEach((state, handle) => {
479
528
  const rigidBody = world.getRigidBody(handle);
@@ -499,7 +548,12 @@ const Physics = ({
499
548
  return;
500
549
  }
501
550
 
502
- state.setMatrix(_matrix4.compose(rapierVector3ToVector3(rigidBody.translation()), rapierQuaternionToQuaternion(rigidBody.rotation()), state.worldScale).premultiply(state.invertedMatrixWorld));
551
+ let oldState = previousTranslations[rigidBody.handle];
552
+ let newTranslation = rapierVector3ToVector3(rigidBody.translation());
553
+ let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
554
+ let interpolatedTranslation = oldState ? rapierVector3ToVector3(oldState.translation).lerp(newTranslation, interpolationAlpha) : newTranslation;
555
+ let interpolatedRotation = oldState ? rapierQuaternionToQuaternion(oldState.rotation).slerp(newRotation, interpolationAlpha) : newRotation;
556
+ state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
503
557
 
504
558
  if (state.mesh instanceof InstancedMesh) {
505
559
  state.mesh.instanceMatrix.needsUpdate = true;
@@ -549,7 +603,6 @@ const Physics = ({
549
603
  });
550
604
  }
551
605
  });
552
- time.current = now;
553
606
  });
554
607
  const api = useMemo(() => createWorldApi(getWorldRef), []);
555
608
  const context = useMemo(() => ({
@@ -561,8 +614,9 @@ const Physics = ({
561
614
  },
562
615
  colliderMeshes,
563
616
  rigidBodyStates,
564
- rigidBodyEvents
565
- }), []);
617
+ rigidBodyEvents,
618
+ isPaused
619
+ }), [isPaused]);
566
620
  return /*#__PURE__*/React.createElement(RapierContext.Provider, {
567
621
  value: context
568
622
  }, children);
@@ -705,9 +759,15 @@ const useRigidBody = (options = {}) => {
705
759
  rigidBody.resetForces(false);
706
760
  rigidBody.resetTorques(false);
707
761
  const colliderSetting = (_ref = (_options$colliders = options === null || options === void 0 ? void 0 : options.colliders) !== null && _options$colliders !== void 0 ? _options$colliders : physicsOptions.colliders) !== null && _ref !== void 0 ? _ref : false;
708
- const autoColliders = colliderSetting !== false ? createCollidersFromChildren(ref.current, rigidBody, _objectSpread2(_objectSpread2({}, options), {}, {
709
- colliders: colliderSetting
710
- }), world) : [];
762
+ const autoColliders = colliderSetting !== false ? createCollidersFromChildren({
763
+ object: ref.current,
764
+ rigidBody,
765
+ options: _objectSpread2(_objectSpread2({}, options), {}, {
766
+ colliders: colliderSetting
767
+ }),
768
+ world,
769
+ ignoreMeshColliders: true
770
+ }) : [];
711
771
  rigidBodyStates.set(rigidBody.handle, {
712
772
  mesh: ref.current,
713
773
  invertedMatrixWorld: ref.current.parent.matrixWorld.clone().invert(),
@@ -872,9 +932,18 @@ const MeshCollider = ({
872
932
  var _ref;
873
933
 
874
934
  const colliderSetting = (_ref = type !== null && type !== void 0 ? type : physicsOptions.colliders) !== null && _ref !== void 0 ? _ref : false;
875
- autoColliders = colliderSetting !== false ? createCollidersFromChildren(object.current, api, _objectSpread2(_objectSpread2({}, options), {}, {
876
- colliders: colliderSetting
877
- }), world, false) : [];
935
+
936
+ if ("raw" in api) {
937
+ autoColliders = createCollidersFromChildren({
938
+ object: object.current,
939
+ rigidBody: api,
940
+ options: _objectSpread2(_objectSpread2({}, options), {}, {
941
+ colliders: colliderSetting
942
+ }),
943
+ world,
944
+ ignoreMeshColliders: false
945
+ });
946
+ }
878
947
  }
879
948
 
880
949
  return () => {
@@ -1021,7 +1090,7 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1021
1090
 
1022
1091
  return instancesRef.current;
1023
1092
  });
1024
- useEffect(() => {
1093
+ useLayoutEffect(() => {
1025
1094
  const colliders = [];
1026
1095
  const rigidBodies = instancesRefGetter.current();
1027
1096
 
@@ -1047,15 +1116,21 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1047
1116
  scale.multiply(s);
1048
1117
  }
1049
1118
 
1050
- const colliderDesc = colliderDescFromGeometry(mesh.geometry, props.colliders || physicsOptions.colliders, scale, false // Collisions currently not enabled for instances
1051
- );
1052
1119
  const rigidBody = world.createRigidBody(rigidBodyDesc);
1053
1120
  const matrix = new Matrix4();
1054
1121
  mesh.getMatrixAt(index, matrix);
1055
1122
  const {
1056
1123
  position,
1057
1124
  rotation
1058
- } = decomposeMatrix4(matrix); // Set positions
1125
+ } = decomposeMatrix4(matrix);
1126
+
1127
+ if (props.colliders !== false) {
1128
+ const colliderDesc = colliderDescFromGeometry(mesh.geometry, props.colliders !== undefined ? props.colliders : physicsOptions.colliders, scale, false // Collisions currently not enabled for instances
1129
+ );
1130
+ const collider = world.createCollider(colliderDesc, rigidBody);
1131
+ colliders.push(collider);
1132
+ } // Set positions
1133
+
1059
1134
 
1060
1135
  if (props.positions && props.positions[index]) {
1061
1136
  rigidBody.setTranslation(vectorArrayToVector3(props.positions[index]), true);
@@ -1071,7 +1146,6 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1071
1146
  rigidBody.setRotation(rotation, true);
1072
1147
  }
1073
1148
 
1074
- const collider = world.createCollider(colliderDesc, rigidBody);
1075
1149
  rigidBodyStates.set(rigidBody.handle, {
1076
1150
  mesh: mesh,
1077
1151
  isSleeping: false,
@@ -1092,7 +1166,6 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1092
1166
  }
1093
1167
 
1094
1168
  });
1095
- colliders.push(collider);
1096
1169
  rigidBodies.push({
1097
1170
  rigidBody,
1098
1171
  api
@@ -1111,10 +1184,19 @@ const InstancedRigidBodies = /*#__PURE__*/forwardRef((props, ref) => {
1111
1184
  };
1112
1185
  }
1113
1186
  }, []);
1114
- useImperativeHandle(ref, () => createInstancedRigidBodiesApi(instancesRefGetter));
1115
- return /*#__PURE__*/React.createElement("object3D", {
1187
+ const api = useMemo(() => createInstancedRigidBodiesApi(instancesRefGetter), []);
1188
+ useImperativeHandle(ref, () => api); // console.log(api);
1189
+
1190
+ return /*#__PURE__*/React.createElement(RigidBodyContext.Provider, {
1191
+ value: {
1192
+ ref: object,
1193
+ api,
1194
+ hasCollisionEvents: false,
1195
+ options: props
1196
+ }
1197
+ }, /*#__PURE__*/React.createElement("object3D", {
1116
1198
  ref: object
1117
- }, props.children);
1199
+ }, props.children));
1118
1200
  });
1119
1201
 
1120
1202
  function _extends() {
@@ -1149,18 +1231,40 @@ const AnyCollider = _ref => {
1149
1231
  const rigidBodyContext = useRigidBodyContext();
1150
1232
  const ref = useRef(null);
1151
1233
  useEffect(() => {
1152
- var _rigidBodyContext$api;
1153
-
1154
1234
  const scale = ref.current.getWorldScale(new Vector3());
1155
- const collider = createColliderFromOptions({
1156
- options: props,
1157
- world,
1158
- rigidBody: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : (_rigidBodyContext$api = rigidBodyContext.api) === null || _rigidBodyContext$api === void 0 ? void 0 : _rigidBodyContext$api.raw(),
1159
- scale,
1160
- hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1161
- });
1235
+ const colliders = []; // If this is an InstancedRigidBody api
1236
+
1237
+ if (rigidBodyContext && "at" in rigidBodyContext.api) {
1238
+ rigidBodyContext.api.forEach((body, index) => {
1239
+ var _rigidBodyContext$opt, _rigidBodyContext$opt2;
1240
+
1241
+ let instanceScale = scale.clone();
1242
+
1243
+ if ("scales" in rigidBodyContext.options && rigidBodyContext !== null && rigidBodyContext !== void 0 && (_rigidBodyContext$opt = rigidBodyContext.options) !== null && _rigidBodyContext$opt !== void 0 && (_rigidBodyContext$opt2 = _rigidBodyContext$opt.scales) !== null && _rigidBodyContext$opt2 !== void 0 && _rigidBodyContext$opt2[index]) {
1244
+ instanceScale.multiply(vectorArrayToVector3(rigidBodyContext.options.scales[index]));
1245
+ }
1246
+
1247
+ colliders.push(createColliderFromOptions({
1248
+ options: props,
1249
+ world,
1250
+ rigidBody: body.raw(),
1251
+ scale: instanceScale,
1252
+ hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1253
+ }));
1254
+ });
1255
+ } else {
1256
+ colliders.push(createColliderFromOptions({
1257
+ options: props,
1258
+ world,
1259
+ // Initiate with a rigidbody, or undefined, because colliders can exist without a rigid body
1260
+ rigidBody: rigidBodyContext && "raw" in rigidBodyContext.api ? rigidBodyContext.api.raw() : undefined,
1261
+ scale,
1262
+ hasCollisionEvents: rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.hasCollisionEvents
1263
+ }));
1264
+ }
1265
+
1162
1266
  return () => {
1163
- world.removeCollider(collider);
1267
+ colliders.forEach(collider => world.removeCollider(collider));
1164
1268
  };
1165
1269
  }, []);
1166
1270
  return /*#__PURE__*/React.createElement("object3D", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/rapier",
3
- "version": "0.6.2",
3
+ "version": "0.6.5",
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
@@ -2,6 +2,11 @@
2
2
  <img src="https://raw.githubusercontent.com/pmndrs/react-three-rapier/HEAD/packages/react-three-rapier/misc/hero.svg" alt="@react-three/rapier" />
3
3
  </p>
4
4
 
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" />
8
+ </p>
9
+
5
10
  <p align="center">⚠️ Under heavy development. All APIs are subject to change. ⚠️</p>
6
11
 
7
12
  ## Usage
@@ -121,6 +126,8 @@ Instanced meshes can also be used and have automatic colliders generated from th
121
126
 
122
127
  By wrapping the `InstancedMesh` in `<InstancedRigidBodies />`, each instance will be attached to an individual `RigidBody`.
123
128
 
129
+ > Note: Custom colliders (compound shapes) for InstancedMesh is currently not supported
130
+
124
131
  ```tsx
125
132
  import { InstancedRigidBodies } from "@react-three/rapier";
126
133
 
@@ -166,6 +173,8 @@ const Scene = () => {
166
173
  <instancedMesh args={[undefined, undefined, COUNT]}>
167
174
  <sphereBufferGeometry args={[0.2]} />
168
175
  <meshPhysicalGeometry color="blue" />
176
+
177
+ <CuboidCollider args={[0.1, 0.2, 0.1]} />
169
178
  </instancedMesh>
170
179
  </InstancedRigidBodies>
171
180
  );
@@ -242,6 +251,7 @@ In order, but also not necessarily:
242
251
  - [x] Collision events
243
252
  - [x] Colliders outside RigidBodies
244
253
  - [x] InstancedMesh support
254
+ - [x] Timestep improvements for determinism
245
255
  - [ ] Normalize and improve collision events (add events to single Colliders, InstancedRigidBodies, etc)
246
256
  - [ ] Docs
247
257
  - [ ] CodeSandbox examples