@react-three/rapier 0.13.2 → 0.14.0-rc.1

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.
@@ -12,13 +12,13 @@ export interface AttractorProps {
12
12
  * The strength of the attractor.
13
13
  * Positive values attract, negative values repel.
14
14
  *
15
- * @default 1
15
+ * @defaultValue 1
16
16
  */
17
17
  strength?: number;
18
18
  /**
19
19
  * The range of the attractor. Will not affect objects outside of this range.
20
20
  *
21
- * @default 10
21
+ * @defaultValue 10
22
22
  * @min 0
23
23
  */
24
24
  range?: number;
@@ -27,12 +27,12 @@ export interface AttractorProps {
27
27
  * - static: The gravity is constant and does not change over time.
28
28
  * - linear: The gravity is linearly interpolated the closer the object is to the attractor.
29
29
  * - newtonian: The gravity is calculated using the newtonian gravity formula.
30
- * @default "static"
30
+ * @defaultValue "static"
31
31
  */
32
32
  type?: AttractorGravityType;
33
33
  /**
34
34
  * The mass of the attractor. Used when type is `newtonian`.
35
- * @default 6.673e-11
35
+ * @defaultValue 6.673e-11
36
36
  */
37
37
  gravitationalConstant?: number;
38
38
  /**
@@ -140,18 +140,11 @@ export interface PhysicsProps {
140
140
  * @defaultValue false
141
141
  */
142
142
  paused?: boolean;
143
- /**
144
- * The update priority at which the physics simulation should run.
145
- *
146
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#taking-over-the-render-loop
147
- * @defaultValue undefined
148
- */
149
- updatePriority?: number;
150
143
  /**
151
144
  * Interpolate the world transform using the frame delta times.
152
145
  * Has no effect if timeStep is set to "vary".
153
146
  *
154
- * @default true
147
+ * @defaultValue true
155
148
  **/
156
149
  interpolate?: boolean;
157
150
  }
@@ -228,31 +228,41 @@ export interface RigidBodyOptions extends ColliderProps {
228
228
  * Specify the type of this rigid body
229
229
  */
230
230
  type?: RigidBodyTypeString;
231
- /** Whether or not this body can sleep.
232
- * default: true
231
+ /**
232
+ * Whether or not this body can sleep.
233
+ * @defaultValue true
233
234
  */
234
235
  canSleep?: boolean;
235
236
  /** The linear damping coefficient of this rigid-body.*/
236
237
  linearDamping?: number;
237
238
  /** The angular damping coefficient of this rigid-body.*/
238
239
  angularDamping?: number;
239
- /** The initial linear velocity of this body.
240
- * default: zero velocity
240
+ /**
241
+ * The initial linear velocity of this body.
242
+ * @defaultValue [0,0,0]
241
243
  */
242
244
  linearVelocity?: Vector3Array;
243
- /** The initial angular velocity of this body.
244
- * Default: zero velocity.
245
+ /**
246
+ * The initial angular velocity of this body.
247
+ * @defaultValue [0,0,0]
245
248
  */
246
249
  angularVelocity?: Vector3Array;
247
250
  /**
248
251
  * The scaling factor applied to the gravity affecting the rigid-body.
249
- * Default: 1.0
252
+ * @defaultValue 1.0
250
253
  */
251
254
  gravityScale?: number;
255
+ /**
256
+ * The dominance group of this RigidBody. If a rigid body has a higher domiance group,
257
+ * on collision it will be immune to forces originating from the other bodies.
258
+ * https://rapier.rs/docs/user_guides/javascript/rigid_bodies#dominance
259
+ * Default: 0
260
+ */
261
+ dominanceGroup?: number;
252
262
  /**
253
263
  * Whether or not Continous Collision Detection is enabled for this rigid-body.
254
264
  * https://rapier.rs/docs/user_guides/javascript/rigid_bodies#continuous-collision-detection
255
- * @default false
265
+ * @defaultValue false
256
266
  */
257
267
  ccd?: boolean;
258
268
  /**
@@ -308,7 +318,7 @@ export interface RigidBodyOptions extends ColliderProps {
308
318
  */
309
319
  enabledRotations?: Boolean3Array;
310
320
  /**
311
- * Allow rotation of this rigid-body only along specific axes.
321
+ * Allow translation of this rigid-body only along specific axes.
312
322
  */
313
323
  enabledTranslations?: Boolean3Array;
314
324
  /**
@@ -31,5 +31,51 @@ export declare const getColliderArgsFromGeometry: (geometry: BufferGeometry, col
31
31
  args: unknown[];
32
32
  offset: Vector3;
33
33
  };
34
- export declare const useColliderEvents: (getCollider: () => Collider, props: ColliderProps, events: EventMap) => void;
34
+ export declare const getActiveCollisionEventsFromProps: (props?: ColliderProps) => {
35
+ collision: boolean;
36
+ contactForce: boolean;
37
+ };
38
+ export declare const useColliderEvents: (getCollider: () => Collider, props: ColliderProps, events: EventMap, activeEvents?: {
39
+ collision?: boolean;
40
+ contactForce?: boolean;
41
+ }) => void;
42
+ export declare const cleanRigidBodyPropsForCollider: (props?: RigidBodyProps) => {
43
+ linearVelocity?: import("..").Vector3Array | undefined;
44
+ angularVelocity?: import("..").Vector3Array | undefined;
45
+ dominanceGroup?: number | undefined;
46
+ position?: import("@react-three/fiber").Vector3 | undefined;
47
+ rotation?: import("@react-three/fiber").Euler | undefined;
48
+ colliders?: RigidBodyAutoCollider | undefined;
49
+ friction?: number | undefined;
50
+ restitution?: number | undefined;
51
+ collisionGroups?: number | undefined;
52
+ solverGroups?: number | undefined;
53
+ onSleep?(): void;
54
+ onWake?(): void;
55
+ lockRotations?: boolean | undefined;
56
+ lockTranslations?: boolean | undefined;
57
+ enabledRotations?: import("..").Boolean3Array | undefined;
58
+ enabledTranslations?: import("..").Boolean3Array | undefined;
59
+ userData?: {
60
+ [key: string]: any;
61
+ } | undefined;
62
+ includeInvisible?: boolean | undefined;
63
+ transformState?: ((state: import("../components/Physics").RigidBodyState) => import("../components/Physics").RigidBodyState) | undefined;
64
+ name?: string | undefined;
65
+ shape?: ColliderShape | undefined;
66
+ args?: any;
67
+ principalAngularInertia?: import("..").Vector3Array | undefined;
68
+ restitutionCombineRule?: import("@dimforge/rapier3d-compat").CoefficientCombineRule | undefined;
69
+ frictionCombineRule?: import("@dimforge/rapier3d-compat").CoefficientCombineRule | undefined;
70
+ quaternion?: import("@react-three/fiber").Quaternion | undefined;
71
+ scale?: import("@react-three/fiber").Vector3 | undefined;
72
+ density?: number | undefined;
73
+ massProperties?: {
74
+ mass: number;
75
+ centerOfMass: import("@dimforge/rapier3d-compat").Vector;
76
+ principalAngularInertia: import("@dimforge/rapier3d-compat").Vector;
77
+ angularInertiaLocalFrame: import("@dimforge/rapier3d-compat").Rotation;
78
+ } | undefined;
79
+ sensor?: boolean | undefined;
80
+ };
35
81
  export {};
@@ -0,0 +1 @@
1
+ export declare const useRaf: (callback: (dt: number) => void) => void;
@@ -178,6 +178,62 @@ function useConst(initialValue) {
178
178
  return ref.current.value;
179
179
  }
180
180
 
181
+ const useRaf = callback => {
182
+ const cb = React.useRef(callback);
183
+ const raf = React.useRef(0);
184
+ const lastFrame = React.useRef(0);
185
+ React.useEffect(() => {
186
+ cb.current = callback;
187
+ }, [callback]);
188
+ React.useEffect(() => {
189
+ const loop = () => {
190
+ const now = performance.now();
191
+ const delta = now - lastFrame.current;
192
+ raf.current = requestAnimationFrame(loop);
193
+ cb.current(delta / 1000);
194
+ lastFrame.current = now;
195
+ };
196
+
197
+ raf.current = requestAnimationFrame(loop);
198
+ return () => cancelAnimationFrame(raf.current);
199
+ }, []);
200
+ };
201
+
202
+ function _objectWithoutPropertiesLoose(source, excluded) {
203
+ if (source == null) return {};
204
+ var target = {};
205
+ var sourceKeys = Object.keys(source);
206
+ var key, i;
207
+
208
+ for (i = 0; i < sourceKeys.length; i++) {
209
+ key = sourceKeys[i];
210
+ if (excluded.indexOf(key) >= 0) continue;
211
+ target[key] = source[key];
212
+ }
213
+
214
+ return target;
215
+ }
216
+
217
+ function _objectWithoutProperties(source, excluded) {
218
+ if (source == null) return {};
219
+ var target = _objectWithoutPropertiesLoose(source, excluded);
220
+ var key, i;
221
+
222
+ if (Object.getOwnPropertySymbols) {
223
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
224
+
225
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
226
+ key = sourceSymbolKeys[i];
227
+ if (excluded.indexOf(key) >= 0) continue;
228
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
229
+ target[key] = source[key];
230
+ }
231
+ }
232
+
233
+ return target;
234
+ }
235
+
236
+ const _excluded$2 = ["mass", "linearDamping", "angularDamping", "type", "onCollisionEnter", "onCollisionExit", "onIntersectionEnter", "onIntersectionExit", "onContactForce", "children", "canSleep", "ccd", "gravityScale"];
181
237
  const scaleColliderArgs = (shape, args, scale) => {
182
238
  const newArgs = args.slice(); // Heightfield uses a vector
183
239
 
@@ -344,7 +400,7 @@ const createColliderPropsFromChildren = ({
344
400
  ignoreMeshColliders: _ignoreMeshColliders = true,
345
401
  options
346
402
  }) => {
347
- const colliderProps = [];
403
+ const childColliderProps = [];
348
404
  object.updateWorldMatrix(true, false);
349
405
  const invertedParentMatrixWorld = object.matrixWorld.clone().invert();
350
406
 
@@ -365,13 +421,16 @@ const createColliderPropsFromChildren = ({
365
421
  args,
366
422
  offset
367
423
  } = getColliderArgsFromGeometry(geometry, options.colliders || "cuboid");
368
- colliderProps.push(_objectSpread2(_objectSpread2({}, options), {}, {
424
+
425
+ const colliderProps = _objectSpread2(_objectSpread2({}, cleanRigidBodyPropsForCollider(options)), {}, {
369
426
  args: args,
370
427
  shape: shape,
371
428
  rotation: [rotationEuler.x, rotationEuler.y, rotationEuler.z],
372
429
  position: [_position.x + offset.x * worldScale.x, _position.y + offset.y * worldScale.y, _position.z + offset.z * worldScale.z],
373
430
  scale: [worldScale.x, worldScale.y, worldScale.z]
374
- }));
431
+ });
432
+
433
+ childColliderProps.push(colliderProps);
375
434
  }
376
435
  };
377
436
 
@@ -381,7 +440,7 @@ const createColliderPropsFromChildren = ({
381
440
  object.traverseVisible(colliderFromChild);
382
441
  }
383
442
 
384
- return colliderProps;
443
+ return childColliderProps;
385
444
  };
386
445
  const getColliderArgsFromGeometry = (geometry, colliders) => {
387
446
  switch (colliders) {
@@ -437,7 +496,17 @@ const getColliderArgsFromGeometry = (geometry, colliders) => {
437
496
  offset: new three.Vector3()
438
497
  };
439
498
  };
440
- const useColliderEvents = (getCollider, props, events) => {
499
+ const getActiveCollisionEventsFromProps = props => {
500
+ return {
501
+ collision: !!(props !== null && props !== void 0 && props.onCollisionEnter || props !== null && props !== void 0 && props.onCollisionExit || props !== null && props !== void 0 && props.onIntersectionEnter || props !== null && props !== void 0 && props.onIntersectionExit),
502
+ contactForce: !!(props !== null && props !== void 0 && props.onContactForce)
503
+ };
504
+ };
505
+ const useColliderEvents = (getCollider, props, events,
506
+ /**
507
+ * The RigidBody can pass down active events to the collider without attaching the event listners
508
+ */
509
+ activeEvents = {}) => {
441
510
  const {
442
511
  onCollisionEnter,
443
512
  onCollisionExit,
@@ -449,8 +518,12 @@ const useColliderEvents = (getCollider, props, events) => {
449
518
  const collider = getCollider();
450
519
 
451
520
  if (collider) {
452
- const hasCollisionEvent = !!(onCollisionEnter || onCollisionExit || onIntersectionEnter || onIntersectionExit);
453
- const hasContactForceEvent = !!onContactForce;
521
+ const {
522
+ collision: collisionEventsActive,
523
+ contactForce: contactForceEventsActive
524
+ } = getActiveCollisionEventsFromProps(props);
525
+ const hasCollisionEvent = collisionEventsActive || activeEvents.collision;
526
+ const hasContactForceEvent = contactForceEventsActive || activeEvents.contactForce;
454
527
 
455
528
  if (hasCollisionEvent && hasContactForceEvent) {
456
529
  collider.setActiveEvents(rapier3dCompat.ActiveEvents.COLLISION_EVENTS | rapier3dCompat.ActiveEvents.CONTACT_FORCE_EVENTS);
@@ -474,7 +547,12 @@ const useColliderEvents = (getCollider, props, events) => {
474
547
  events.delete(collider.handle);
475
548
  }
476
549
  };
477
- }, [onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce]);
550
+ }, [onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce, activeEvents]);
551
+ };
552
+ const cleanRigidBodyPropsForCollider = (props = {}) => {
553
+ const rest = _objectWithoutProperties(props, _excluded$2);
554
+
555
+ return rest;
478
556
  };
479
557
 
480
558
  const useMutableCallback = fn => {
@@ -675,10 +753,12 @@ const Physics = ({
675
753
  children,
676
754
  timeStep: _timeStep = 1 / 60,
677
755
  paused: _paused = false,
678
- updatePriority,
679
756
  interpolate: _interpolate = true
680
757
  }) => {
681
758
  const rapier = useAsset.useAsset(importRapier);
759
+ const {
760
+ invalidate
761
+ } = fiber.useThree();
682
762
  const worldRef = React.useRef();
683
763
  const getWorldRef = React.useRef(() => {
684
764
  if (!worldRef.current) {
@@ -959,10 +1039,13 @@ const Physics = ({
959
1039
  maxForceMagnitude: event.maxForceMagnitude()
960
1040
  }));
961
1041
  });
1042
+ world.forEachActiveRigidBody(body => {
1043
+ invalidate();
1044
+ });
962
1045
  }, [_paused, _timeStep, _interpolate]);
963
- fiber.useFrame((_, dt) => {
1046
+ useRaf(dt => {
964
1047
  if (!_paused) step(dt);
965
- }, updatePriority);
1048
+ });
966
1049
  const context = React.useMemo(() => ({
967
1050
  rapier,
968
1051
  world: api,
@@ -1002,40 +1085,6 @@ function _extends() {
1002
1085
  return _extends.apply(this, arguments);
1003
1086
  }
1004
1087
 
1005
- function _objectWithoutPropertiesLoose(source, excluded) {
1006
- if (source == null) return {};
1007
- var target = {};
1008
- var sourceKeys = Object.keys(source);
1009
- var key, i;
1010
-
1011
- for (i = 0; i < sourceKeys.length; i++) {
1012
- key = sourceKeys[i];
1013
- if (excluded.indexOf(key) >= 0) continue;
1014
- target[key] = source[key];
1015
- }
1016
-
1017
- return target;
1018
- }
1019
-
1020
- function _objectWithoutProperties(source, excluded) {
1021
- if (source == null) return {};
1022
- var target = _objectWithoutPropertiesLoose(source, excluded);
1023
- var key, i;
1024
-
1025
- if (Object.getOwnPropertySymbols) {
1026
- var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
1027
-
1028
- for (i = 0; i < sourceSymbolKeys.length; i++) {
1029
- key = sourceSymbolKeys[i];
1030
- if (excluded.indexOf(key) >= 0) continue;
1031
- if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
1032
- target[key] = source[key];
1033
- }
1034
- }
1035
-
1036
- return target;
1037
- }
1038
-
1039
1088
  /**
1040
1089
  * Initiate an instance and return a safe getter
1041
1090
  */
@@ -1148,10 +1197,10 @@ const AnyCollider = /*#__PURE__*/React.memo( /*#__PURE__*/React.forwardRef((prop
1148
1197
  }, []);
1149
1198
  React.useImperativeHandle(forwardedRef, () => getInstance());
1150
1199
  const mergedProps = React.useMemo(() => {
1151
- return _objectSpread2(_objectSpread2({}, rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options), props);
1200
+ return _objectSpread2(_objectSpread2({}, cleanRigidBodyPropsForCollider(rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options)), props);
1152
1201
  }, [props, rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options]);
1153
1202
  useUpdateColliderOptions(getInstance, mergedProps, colliderStates);
1154
- useColliderEvents(getInstance, mergedProps, colliderEvents);
1203
+ useColliderEvents(getInstance, mergedProps, colliderEvents, getActiveCollisionEventsFromProps(rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options));
1155
1204
  return /*#__PURE__*/React__default["default"].createElement("object3D", {
1156
1205
  position: position,
1157
1206
  rotation: rotation,
@@ -1312,6 +1361,9 @@ const mutableRigidBodyOptions = {
1312
1361
  angularDamping: (rb, value) => {
1313
1362
  rb.setAngularDamping(value);
1314
1363
  },
1364
+ dominanceGroup: (rb, value) => {
1365
+ rb.setDominanceGroup(value);
1366
+ },
1315
1367
  enabledRotations: (rb, [x, y, z]) => {
1316
1368
  rb.setEnabledRotations(x, y, z, true);
1317
1369
  },
@@ -1396,7 +1448,8 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1396
1448
  onCollisionEnter,
1397
1449
  onCollisionExit,
1398
1450
  onIntersectionEnter,
1399
- onIntersectionExit
1451
+ onIntersectionExit,
1452
+ onContactForce
1400
1453
  } = props;
1401
1454
  const eventHandlers = {
1402
1455
  onWake,
@@ -1404,7 +1457,8 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1404
1457
  onCollisionEnter,
1405
1458
  onCollisionExit,
1406
1459
  onIntersectionEnter,
1407
- onIntersectionExit
1460
+ onIntersectionExit,
1461
+ onContactForce
1408
1462
  };
1409
1463
  React.useEffect(() => {
1410
1464
  const rigidBody = getRigidBody();
@@ -1412,7 +1466,7 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1412
1466
  return () => {
1413
1467
  events.delete(rigidBody.handle);
1414
1468
  };
1415
- }, [onWake, onSleep, onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit]);
1469
+ }, [onWake, onSleep, onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce]);
1416
1470
  };
1417
1471
 
1418
1472
  const _excluded$1 = ["children", "type", "position", "rotation", "scale", "quaternion", "transformState"];
@@ -178,6 +178,62 @@ function useConst(initialValue) {
178
178
  return ref.current.value;
179
179
  }
180
180
 
181
+ const useRaf = callback => {
182
+ const cb = React.useRef(callback);
183
+ const raf = React.useRef(0);
184
+ const lastFrame = React.useRef(0);
185
+ React.useEffect(() => {
186
+ cb.current = callback;
187
+ }, [callback]);
188
+ React.useEffect(() => {
189
+ const loop = () => {
190
+ const now = performance.now();
191
+ const delta = now - lastFrame.current;
192
+ raf.current = requestAnimationFrame(loop);
193
+ cb.current(delta / 1000);
194
+ lastFrame.current = now;
195
+ };
196
+
197
+ raf.current = requestAnimationFrame(loop);
198
+ return () => cancelAnimationFrame(raf.current);
199
+ }, []);
200
+ };
201
+
202
+ function _objectWithoutPropertiesLoose(source, excluded) {
203
+ if (source == null) return {};
204
+ var target = {};
205
+ var sourceKeys = Object.keys(source);
206
+ var key, i;
207
+
208
+ for (i = 0; i < sourceKeys.length; i++) {
209
+ key = sourceKeys[i];
210
+ if (excluded.indexOf(key) >= 0) continue;
211
+ target[key] = source[key];
212
+ }
213
+
214
+ return target;
215
+ }
216
+
217
+ function _objectWithoutProperties(source, excluded) {
218
+ if (source == null) return {};
219
+ var target = _objectWithoutPropertiesLoose(source, excluded);
220
+ var key, i;
221
+
222
+ if (Object.getOwnPropertySymbols) {
223
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
224
+
225
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
226
+ key = sourceSymbolKeys[i];
227
+ if (excluded.indexOf(key) >= 0) continue;
228
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
229
+ target[key] = source[key];
230
+ }
231
+ }
232
+
233
+ return target;
234
+ }
235
+
236
+ const _excluded$2 = ["mass", "linearDamping", "angularDamping", "type", "onCollisionEnter", "onCollisionExit", "onIntersectionEnter", "onIntersectionExit", "onContactForce", "children", "canSleep", "ccd", "gravityScale"];
181
237
  const scaleColliderArgs = (shape, args, scale) => {
182
238
  const newArgs = args.slice(); // Heightfield uses a vector
183
239
 
@@ -344,7 +400,7 @@ const createColliderPropsFromChildren = ({
344
400
  ignoreMeshColliders: _ignoreMeshColliders = true,
345
401
  options
346
402
  }) => {
347
- const colliderProps = [];
403
+ const childColliderProps = [];
348
404
  object.updateWorldMatrix(true, false);
349
405
  const invertedParentMatrixWorld = object.matrixWorld.clone().invert();
350
406
 
@@ -365,13 +421,16 @@ const createColliderPropsFromChildren = ({
365
421
  args,
366
422
  offset
367
423
  } = getColliderArgsFromGeometry(geometry, options.colliders || "cuboid");
368
- colliderProps.push(_objectSpread2(_objectSpread2({}, options), {}, {
424
+
425
+ const colliderProps = _objectSpread2(_objectSpread2({}, cleanRigidBodyPropsForCollider(options)), {}, {
369
426
  args: args,
370
427
  shape: shape,
371
428
  rotation: [rotationEuler.x, rotationEuler.y, rotationEuler.z],
372
429
  position: [_position.x + offset.x * worldScale.x, _position.y + offset.y * worldScale.y, _position.z + offset.z * worldScale.z],
373
430
  scale: [worldScale.x, worldScale.y, worldScale.z]
374
- }));
431
+ });
432
+
433
+ childColliderProps.push(colliderProps);
375
434
  }
376
435
  };
377
436
 
@@ -381,7 +440,7 @@ const createColliderPropsFromChildren = ({
381
440
  object.traverseVisible(colliderFromChild);
382
441
  }
383
442
 
384
- return colliderProps;
443
+ return childColliderProps;
385
444
  };
386
445
  const getColliderArgsFromGeometry = (geometry, colliders) => {
387
446
  switch (colliders) {
@@ -437,7 +496,17 @@ const getColliderArgsFromGeometry = (geometry, colliders) => {
437
496
  offset: new three.Vector3()
438
497
  };
439
498
  };
440
- const useColliderEvents = (getCollider, props, events) => {
499
+ const getActiveCollisionEventsFromProps = props => {
500
+ return {
501
+ collision: !!(props !== null && props !== void 0 && props.onCollisionEnter || props !== null && props !== void 0 && props.onCollisionExit || props !== null && props !== void 0 && props.onIntersectionEnter || props !== null && props !== void 0 && props.onIntersectionExit),
502
+ contactForce: !!(props !== null && props !== void 0 && props.onContactForce)
503
+ };
504
+ };
505
+ const useColliderEvents = (getCollider, props, events,
506
+ /**
507
+ * The RigidBody can pass down active events to the collider without attaching the event listners
508
+ */
509
+ activeEvents = {}) => {
441
510
  const {
442
511
  onCollisionEnter,
443
512
  onCollisionExit,
@@ -449,8 +518,12 @@ const useColliderEvents = (getCollider, props, events) => {
449
518
  const collider = getCollider();
450
519
 
451
520
  if (collider) {
452
- const hasCollisionEvent = !!(onCollisionEnter || onCollisionExit || onIntersectionEnter || onIntersectionExit);
453
- const hasContactForceEvent = !!onContactForce;
521
+ const {
522
+ collision: collisionEventsActive,
523
+ contactForce: contactForceEventsActive
524
+ } = getActiveCollisionEventsFromProps(props);
525
+ const hasCollisionEvent = collisionEventsActive || activeEvents.collision;
526
+ const hasContactForceEvent = contactForceEventsActive || activeEvents.contactForce;
454
527
 
455
528
  if (hasCollisionEvent && hasContactForceEvent) {
456
529
  collider.setActiveEvents(rapier3dCompat.ActiveEvents.COLLISION_EVENTS | rapier3dCompat.ActiveEvents.CONTACT_FORCE_EVENTS);
@@ -474,7 +547,12 @@ const useColliderEvents = (getCollider, props, events) => {
474
547
  events.delete(collider.handle);
475
548
  }
476
549
  };
477
- }, [onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce]);
550
+ }, [onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce, activeEvents]);
551
+ };
552
+ const cleanRigidBodyPropsForCollider = (props = {}) => {
553
+ const rest = _objectWithoutProperties(props, _excluded$2);
554
+
555
+ return rest;
478
556
  };
479
557
 
480
558
  const useMutableCallback = fn => {
@@ -675,10 +753,12 @@ const Physics = ({
675
753
  children,
676
754
  timeStep: _timeStep = 1 / 60,
677
755
  paused: _paused = false,
678
- updatePriority,
679
756
  interpolate: _interpolate = true
680
757
  }) => {
681
758
  const rapier = useAsset.useAsset(importRapier);
759
+ const {
760
+ invalidate
761
+ } = fiber.useThree();
682
762
  const worldRef = React.useRef();
683
763
  const getWorldRef = React.useRef(() => {
684
764
  if (!worldRef.current) {
@@ -959,10 +1039,13 @@ const Physics = ({
959
1039
  maxForceMagnitude: event.maxForceMagnitude()
960
1040
  }));
961
1041
  });
1042
+ world.forEachActiveRigidBody(body => {
1043
+ invalidate();
1044
+ });
962
1045
  }, [_paused, _timeStep, _interpolate]);
963
- fiber.useFrame((_, dt) => {
1046
+ useRaf(dt => {
964
1047
  if (!_paused) step(dt);
965
- }, updatePriority);
1048
+ });
966
1049
  const context = React.useMemo(() => ({
967
1050
  rapier,
968
1051
  world: api,
@@ -1002,40 +1085,6 @@ function _extends() {
1002
1085
  return _extends.apply(this, arguments);
1003
1086
  }
1004
1087
 
1005
- function _objectWithoutPropertiesLoose(source, excluded) {
1006
- if (source == null) return {};
1007
- var target = {};
1008
- var sourceKeys = Object.keys(source);
1009
- var key, i;
1010
-
1011
- for (i = 0; i < sourceKeys.length; i++) {
1012
- key = sourceKeys[i];
1013
- if (excluded.indexOf(key) >= 0) continue;
1014
- target[key] = source[key];
1015
- }
1016
-
1017
- return target;
1018
- }
1019
-
1020
- function _objectWithoutProperties(source, excluded) {
1021
- if (source == null) return {};
1022
- var target = _objectWithoutPropertiesLoose(source, excluded);
1023
- var key, i;
1024
-
1025
- if (Object.getOwnPropertySymbols) {
1026
- var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
1027
-
1028
- for (i = 0; i < sourceSymbolKeys.length; i++) {
1029
- key = sourceSymbolKeys[i];
1030
- if (excluded.indexOf(key) >= 0) continue;
1031
- if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
1032
- target[key] = source[key];
1033
- }
1034
- }
1035
-
1036
- return target;
1037
- }
1038
-
1039
1088
  /**
1040
1089
  * Initiate an instance and return a safe getter
1041
1090
  */
@@ -1148,10 +1197,10 @@ const AnyCollider = /*#__PURE__*/React.memo( /*#__PURE__*/React.forwardRef((prop
1148
1197
  }, []);
1149
1198
  React.useImperativeHandle(forwardedRef, () => getInstance());
1150
1199
  const mergedProps = React.useMemo(() => {
1151
- return _objectSpread2(_objectSpread2({}, rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options), props);
1200
+ return _objectSpread2(_objectSpread2({}, cleanRigidBodyPropsForCollider(rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options)), props);
1152
1201
  }, [props, rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options]);
1153
1202
  useUpdateColliderOptions(getInstance, mergedProps, colliderStates);
1154
- useColliderEvents(getInstance, mergedProps, colliderEvents);
1203
+ useColliderEvents(getInstance, mergedProps, colliderEvents, getActiveCollisionEventsFromProps(rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options));
1155
1204
  return /*#__PURE__*/React__default["default"].createElement("object3D", {
1156
1205
  position: position,
1157
1206
  rotation: rotation,
@@ -1312,6 +1361,9 @@ const mutableRigidBodyOptions = {
1312
1361
  angularDamping: (rb, value) => {
1313
1362
  rb.setAngularDamping(value);
1314
1363
  },
1364
+ dominanceGroup: (rb, value) => {
1365
+ rb.setDominanceGroup(value);
1366
+ },
1315
1367
  enabledRotations: (rb, [x, y, z]) => {
1316
1368
  rb.setEnabledRotations(x, y, z, true);
1317
1369
  },
@@ -1396,7 +1448,8 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1396
1448
  onCollisionEnter,
1397
1449
  onCollisionExit,
1398
1450
  onIntersectionEnter,
1399
- onIntersectionExit
1451
+ onIntersectionExit,
1452
+ onContactForce
1400
1453
  } = props;
1401
1454
  const eventHandlers = {
1402
1455
  onWake,
@@ -1404,7 +1457,8 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1404
1457
  onCollisionEnter,
1405
1458
  onCollisionExit,
1406
1459
  onIntersectionEnter,
1407
- onIntersectionExit
1460
+ onIntersectionExit,
1461
+ onContactForce
1408
1462
  };
1409
1463
  React.useEffect(() => {
1410
1464
  const rigidBody = getRigidBody();
@@ -1412,7 +1466,7 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1412
1466
  return () => {
1413
1467
  events.delete(rigidBody.handle);
1414
1468
  };
1415
- }, [onWake, onSleep, onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit]);
1469
+ }, [onWake, onSleep, onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce]);
1416
1470
  };
1417
1471
 
1418
1472
  const _excluded$1 = ["children", "type", "position", "rotation", "scale", "quaternion", "transformState"];
@@ -1,7 +1,7 @@
1
1
  import { ActiveEvents, ColliderDesc, EventQueue, RigidBodyDesc } from '@dimforge/rapier3d-compat';
2
2
  export { CoefficientCombineRule, Collider as RapierCollider, RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat';
3
- import { useFrame, useThree } from '@react-three/fiber';
4
- import React, { useRef, useMemo, useEffect, useContext, useState, memo, createContext, useCallback, forwardRef, useImperativeHandle, Fragment } from 'react';
3
+ import { useThree, useFrame } from '@react-three/fiber';
4
+ import React, { useRef, useEffect, useMemo, useContext, useState, memo, createContext, useCallback, forwardRef, useImperativeHandle, Fragment } from 'react';
5
5
  import { Quaternion, Euler, Vector3, Object3D, Matrix4, MathUtils, BufferAttribute, DynamicDrawUsage } from 'three';
6
6
  import { useAsset } from 'use-asset';
7
7
  import { mergeVertices, VertexNormalsHelper } from 'three-stdlib';
@@ -153,6 +153,62 @@ function useConst(initialValue) {
153
153
  return ref.current.value;
154
154
  }
155
155
 
156
+ const useRaf = callback => {
157
+ const cb = useRef(callback);
158
+ const raf = useRef(0);
159
+ const lastFrame = useRef(0);
160
+ useEffect(() => {
161
+ cb.current = callback;
162
+ }, [callback]);
163
+ useEffect(() => {
164
+ const loop = () => {
165
+ const now = performance.now();
166
+ const delta = now - lastFrame.current;
167
+ raf.current = requestAnimationFrame(loop);
168
+ cb.current(delta / 1000);
169
+ lastFrame.current = now;
170
+ };
171
+
172
+ raf.current = requestAnimationFrame(loop);
173
+ return () => cancelAnimationFrame(raf.current);
174
+ }, []);
175
+ };
176
+
177
+ function _objectWithoutPropertiesLoose(source, excluded) {
178
+ if (source == null) return {};
179
+ var target = {};
180
+ var sourceKeys = Object.keys(source);
181
+ var key, i;
182
+
183
+ for (i = 0; i < sourceKeys.length; i++) {
184
+ key = sourceKeys[i];
185
+ if (excluded.indexOf(key) >= 0) continue;
186
+ target[key] = source[key];
187
+ }
188
+
189
+ return target;
190
+ }
191
+
192
+ function _objectWithoutProperties(source, excluded) {
193
+ if (source == null) return {};
194
+ var target = _objectWithoutPropertiesLoose(source, excluded);
195
+ var key, i;
196
+
197
+ if (Object.getOwnPropertySymbols) {
198
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
199
+
200
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
201
+ key = sourceSymbolKeys[i];
202
+ if (excluded.indexOf(key) >= 0) continue;
203
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
204
+ target[key] = source[key];
205
+ }
206
+ }
207
+
208
+ return target;
209
+ }
210
+
211
+ const _excluded$2 = ["mass", "linearDamping", "angularDamping", "type", "onCollisionEnter", "onCollisionExit", "onIntersectionEnter", "onIntersectionExit", "onContactForce", "children", "canSleep", "ccd", "gravityScale"];
156
212
  const scaleColliderArgs = (shape, args, scale) => {
157
213
  const newArgs = args.slice(); // Heightfield uses a vector
158
214
 
@@ -319,7 +375,7 @@ const createColliderPropsFromChildren = ({
319
375
  ignoreMeshColliders: _ignoreMeshColliders = true,
320
376
  options
321
377
  }) => {
322
- const colliderProps = [];
378
+ const childColliderProps = [];
323
379
  object.updateWorldMatrix(true, false);
324
380
  const invertedParentMatrixWorld = object.matrixWorld.clone().invert();
325
381
 
@@ -340,13 +396,16 @@ const createColliderPropsFromChildren = ({
340
396
  args,
341
397
  offset
342
398
  } = getColliderArgsFromGeometry(geometry, options.colliders || "cuboid");
343
- colliderProps.push(_objectSpread2(_objectSpread2({}, options), {}, {
399
+
400
+ const colliderProps = _objectSpread2(_objectSpread2({}, cleanRigidBodyPropsForCollider(options)), {}, {
344
401
  args: args,
345
402
  shape: shape,
346
403
  rotation: [rotationEuler.x, rotationEuler.y, rotationEuler.z],
347
404
  position: [_position.x + offset.x * worldScale.x, _position.y + offset.y * worldScale.y, _position.z + offset.z * worldScale.z],
348
405
  scale: [worldScale.x, worldScale.y, worldScale.z]
349
- }));
406
+ });
407
+
408
+ childColliderProps.push(colliderProps);
350
409
  }
351
410
  };
352
411
 
@@ -356,7 +415,7 @@ const createColliderPropsFromChildren = ({
356
415
  object.traverseVisible(colliderFromChild);
357
416
  }
358
417
 
359
- return colliderProps;
418
+ return childColliderProps;
360
419
  };
361
420
  const getColliderArgsFromGeometry = (geometry, colliders) => {
362
421
  switch (colliders) {
@@ -412,7 +471,17 @@ const getColliderArgsFromGeometry = (geometry, colliders) => {
412
471
  offset: new Vector3()
413
472
  };
414
473
  };
415
- const useColliderEvents = (getCollider, props, events) => {
474
+ const getActiveCollisionEventsFromProps = props => {
475
+ return {
476
+ collision: !!(props !== null && props !== void 0 && props.onCollisionEnter || props !== null && props !== void 0 && props.onCollisionExit || props !== null && props !== void 0 && props.onIntersectionEnter || props !== null && props !== void 0 && props.onIntersectionExit),
477
+ contactForce: !!(props !== null && props !== void 0 && props.onContactForce)
478
+ };
479
+ };
480
+ const useColliderEvents = (getCollider, props, events,
481
+ /**
482
+ * The RigidBody can pass down active events to the collider without attaching the event listners
483
+ */
484
+ activeEvents = {}) => {
416
485
  const {
417
486
  onCollisionEnter,
418
487
  onCollisionExit,
@@ -424,8 +493,12 @@ const useColliderEvents = (getCollider, props, events) => {
424
493
  const collider = getCollider();
425
494
 
426
495
  if (collider) {
427
- const hasCollisionEvent = !!(onCollisionEnter || onCollisionExit || onIntersectionEnter || onIntersectionExit);
428
- const hasContactForceEvent = !!onContactForce;
496
+ const {
497
+ collision: collisionEventsActive,
498
+ contactForce: contactForceEventsActive
499
+ } = getActiveCollisionEventsFromProps(props);
500
+ const hasCollisionEvent = collisionEventsActive || activeEvents.collision;
501
+ const hasContactForceEvent = contactForceEventsActive || activeEvents.contactForce;
429
502
 
430
503
  if (hasCollisionEvent && hasContactForceEvent) {
431
504
  collider.setActiveEvents(ActiveEvents.COLLISION_EVENTS | ActiveEvents.CONTACT_FORCE_EVENTS);
@@ -449,7 +522,12 @@ const useColliderEvents = (getCollider, props, events) => {
449
522
  events.delete(collider.handle);
450
523
  }
451
524
  };
452
- }, [onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce]);
525
+ }, [onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce, activeEvents]);
526
+ };
527
+ const cleanRigidBodyPropsForCollider = (props = {}) => {
528
+ const rest = _objectWithoutProperties(props, _excluded$2);
529
+
530
+ return rest;
453
531
  };
454
532
 
455
533
  const useMutableCallback = fn => {
@@ -650,10 +728,12 @@ const Physics = ({
650
728
  children,
651
729
  timeStep: _timeStep = 1 / 60,
652
730
  paused: _paused = false,
653
- updatePriority,
654
731
  interpolate: _interpolate = true
655
732
  }) => {
656
733
  const rapier = useAsset(importRapier);
734
+ const {
735
+ invalidate
736
+ } = useThree();
657
737
  const worldRef = useRef();
658
738
  const getWorldRef = useRef(() => {
659
739
  if (!worldRef.current) {
@@ -934,10 +1014,13 @@ const Physics = ({
934
1014
  maxForceMagnitude: event.maxForceMagnitude()
935
1015
  }));
936
1016
  });
1017
+ world.forEachActiveRigidBody(body => {
1018
+ invalidate();
1019
+ });
937
1020
  }, [_paused, _timeStep, _interpolate]);
938
- useFrame((_, dt) => {
1021
+ useRaf(dt => {
939
1022
  if (!_paused) step(dt);
940
- }, updatePriority);
1023
+ });
941
1024
  const context = useMemo(() => ({
942
1025
  rapier,
943
1026
  world: api,
@@ -977,40 +1060,6 @@ function _extends() {
977
1060
  return _extends.apply(this, arguments);
978
1061
  }
979
1062
 
980
- function _objectWithoutPropertiesLoose(source, excluded) {
981
- if (source == null) return {};
982
- var target = {};
983
- var sourceKeys = Object.keys(source);
984
- var key, i;
985
-
986
- for (i = 0; i < sourceKeys.length; i++) {
987
- key = sourceKeys[i];
988
- if (excluded.indexOf(key) >= 0) continue;
989
- target[key] = source[key];
990
- }
991
-
992
- return target;
993
- }
994
-
995
- function _objectWithoutProperties(source, excluded) {
996
- if (source == null) return {};
997
- var target = _objectWithoutPropertiesLoose(source, excluded);
998
- var key, i;
999
-
1000
- if (Object.getOwnPropertySymbols) {
1001
- var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
1002
-
1003
- for (i = 0; i < sourceSymbolKeys.length; i++) {
1004
- key = sourceSymbolKeys[i];
1005
- if (excluded.indexOf(key) >= 0) continue;
1006
- if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
1007
- target[key] = source[key];
1008
- }
1009
- }
1010
-
1011
- return target;
1012
- }
1013
-
1014
1063
  /**
1015
1064
  * Initiate an instance and return a safe getter
1016
1065
  */
@@ -1123,10 +1172,10 @@ const AnyCollider = /*#__PURE__*/memo( /*#__PURE__*/forwardRef((props, forwarded
1123
1172
  }, []);
1124
1173
  useImperativeHandle(forwardedRef, () => getInstance());
1125
1174
  const mergedProps = useMemo(() => {
1126
- return _objectSpread2(_objectSpread2({}, rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options), props);
1175
+ return _objectSpread2(_objectSpread2({}, cleanRigidBodyPropsForCollider(rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options)), props);
1127
1176
  }, [props, rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options]);
1128
1177
  useUpdateColliderOptions(getInstance, mergedProps, colliderStates);
1129
- useColliderEvents(getInstance, mergedProps, colliderEvents);
1178
+ useColliderEvents(getInstance, mergedProps, colliderEvents, getActiveCollisionEventsFromProps(rigidBodyContext === null || rigidBodyContext === void 0 ? void 0 : rigidBodyContext.options));
1130
1179
  return /*#__PURE__*/React.createElement("object3D", {
1131
1180
  position: position,
1132
1181
  rotation: rotation,
@@ -1287,6 +1336,9 @@ const mutableRigidBodyOptions = {
1287
1336
  angularDamping: (rb, value) => {
1288
1337
  rb.setAngularDamping(value);
1289
1338
  },
1339
+ dominanceGroup: (rb, value) => {
1340
+ rb.setDominanceGroup(value);
1341
+ },
1290
1342
  enabledRotations: (rb, [x, y, z]) => {
1291
1343
  rb.setEnabledRotations(x, y, z, true);
1292
1344
  },
@@ -1371,7 +1423,8 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1371
1423
  onCollisionEnter,
1372
1424
  onCollisionExit,
1373
1425
  onIntersectionEnter,
1374
- onIntersectionExit
1426
+ onIntersectionExit,
1427
+ onContactForce
1375
1428
  } = props;
1376
1429
  const eventHandlers = {
1377
1430
  onWake,
@@ -1379,7 +1432,8 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1379
1432
  onCollisionEnter,
1380
1433
  onCollisionExit,
1381
1434
  onIntersectionEnter,
1382
- onIntersectionExit
1435
+ onIntersectionExit,
1436
+ onContactForce
1383
1437
  };
1384
1438
  useEffect(() => {
1385
1439
  const rigidBody = getRigidBody();
@@ -1387,7 +1441,7 @@ const useRigidBodyEvents = (getRigidBody, props, events) => {
1387
1441
  return () => {
1388
1442
  events.delete(rigidBody.handle);
1389
1443
  };
1390
- }, [onWake, onSleep, onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit]);
1444
+ }, [onWake, onSleep, onCollisionEnter, onCollisionExit, onIntersectionEnter, onIntersectionExit, onContactForce]);
1391
1445
  };
1392
1446
 
1393
1447
  const _excluded$1 = ["children", "type", "position", "rotation", "scale", "quaternion", "transformState"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/rapier",
3
- "version": "0.13.2",
3
+ "version": "0.14.0-rc.1",
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
@@ -84,6 +84,7 @@ For full API outline and documentation, see 🧩 [API Docs](https://pmndrs.githu
84
84
  - [🖼 Joints Example](#-joints-example)
85
85
  - [Advanced hooks usage](#advanced-hooks-usage)
86
86
  - [Manual stepping](#manual-stepping)
87
+ - [On-demand rendering](#on-demand-rendering)
87
88
 
88
89
  ---
89
90
 
@@ -131,7 +132,7 @@ Supported values:
131
132
 
132
133
  - `"cuboid"`, creates a CuboidCollider based on the bounding box of the mesh
133
134
  - `"ball"`, creates a SphereCollider based on the bounding sphere of the mesh
134
- - `"trimesh"`, creates a TrimeshCollider based on the mesh's geometry -- note trimeshes are massless by default (https://rapier.rs/docs/user_guides/javascript/common_mistakes#rigid-body-isnt-affected-by-gravity)
135
+ - `"trimesh"`, creates a TrimeshCollider based on the mesh's geometry
135
136
  - `"hull"`, creates a ConvexHullCollider based on the mesh's geometry
136
137
  - `false`, disables auto-generation
137
138
 
@@ -758,7 +759,7 @@ const JointedThing = () => {
758
759
  [
759
760
  [0, 0, 0], // Position of the joint in bodyA's local space
760
761
  [0, 0, 0], // Position of the joint in bodyB's local space
761
- [0, 0, 0], // Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to.
762
+ [0, 1, 0], // Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to. Cannot be [0,0,0].
762
763
  ]);
763
764
 
764
765
  useEffect(() => {
@@ -793,7 +794,7 @@ const JointedThing = () => {
793
794
  [
794
795
  [0, 0, 0], // Position of the joint in bodyA's local space
795
796
  [0, 0, 0], // Position of the joint in bodyB's local space
796
- [0, 0, 0], // Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to.
797
+ [0, 1, 0], // Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to. Cannot be [0,0,0].
797
798
  ]);
798
799
 
799
800
  return (
@@ -834,4 +835,7 @@ You can manually step the physics simulation by calling the `step` method from t
834
835
  const { step } = useRapier();
835
836
 
836
837
  step(1 / 60);
837
- ```
838
+ ```
839
+
840
+ ### On-demand rendering
841
+ `@react-three/rapier` runs the physics simulation independently from the render loop, and will tell `@react-three/fiber` to render if the scene has active (non-sleeping) RigidBodies. This allows you to use the `<Canvas frameloop="demand" />` (https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#on-demand-rendering) strategy to only render the scene when needed.