@react-three/rapier 1.2.1 → 1.3.0

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.
@@ -149,30 +149,34 @@ export interface PhysicsProps {
149
149
  */
150
150
  gravity?: Vector3Tuple;
151
151
  /**
152
- * The maximum velocity iterations the velocity-based constraint solver can make to attempt
153
- * to remove the energy introduced by constraint stabilization.
154
- *
155
- * @defaultValue 1
152
+ * Amount of penetration the engine wont attempt to correct
153
+ * @defaultValue 0.001
156
154
  */
157
- maxStabilizationIterations?: number;
155
+ allowedLinearError?: number;
158
156
  /**
159
- * The maximum velocity iterations the velocity-based friction constraint solver can make.
160
- *
161
- * The greater this value is, the most realistic friction will be.
157
+ * The number of solver iterations run by the constraints solver for calculating forces.
158
+ * The greater this value is, the most rigid and realistic the physics simulation will be.
162
159
  * However a greater number of iterations is more computationally intensive.
163
160
  *
164
- * @defaultValue 8
161
+ * @defaultValue 4
165
162
  */
166
- maxVelocityFrictionIterations?: number;
163
+ numSolverIterations?: number;
167
164
  /**
168
- * The maximum velocity iterations the velocity-based force constraint solver can make.
169
- *
170
- * The greater this value is, the most rigid and realistic the physics simulation will be.
165
+ * Number of addition friction resolution iteration run during the last solver sub-step.
166
+ * The greater this value is, the most realistic friction will be.
171
167
  * However a greater number of iterations is more computationally intensive.
172
168
  *
173
169
  * @defaultValue 4
174
170
  */
175
- maxVelocityIterations?: number;
171
+ numAdditionalFrictionIterations?: number;
172
+ /**
173
+ * Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration.
174
+ * Increasing this parameter will improve stability of the simulation. It will have a lesser effect than
175
+ * increasing `numSolverIterations` but is also less computationally expensive.
176
+ *
177
+ * @defaultValue 1
178
+ */
179
+ numInternalPgsIterations?: number;
176
180
  /**
177
181
  * The maximal distance separating two objects that will generate predictive contacts
178
182
  *
@@ -181,7 +185,20 @@ export interface PhysicsProps {
181
185
  */
182
186
  predictionDistance?: number;
183
187
  /**
184
- * The Error Reduction Parameter in between 0 and 1, is the proportion of the positional error to be corrected at each time step
188
+ * Minimum number of dynamic bodies in each active island
189
+ *
190
+ * @defaultValue 128
191
+ */
192
+ minIslandSize?: number;
193
+ /**
194
+ * Maximum number of substeps performed by the solver
195
+ *
196
+ * @defaultValue 1
197
+ */
198
+ maxCcdSubsteps?: number;
199
+ /**
200
+ * The Error Reduction Parameter in between 0 and 1, is the proportion of the positional error to be corrected at each time step.
201
+ *
185
202
  * @defaultValue 0.8
186
203
  */
187
204
  erp?: number;
@@ -1,11 +1,11 @@
1
- import { ImpulseJoint, FixedImpulseJoint, SphericalImpulseJoint, RevoluteImpulseJoint, PrismaticImpulseJoint } from "@dimforge/rapier3d-compat";
2
- import React, { RefObject } from "react";
3
- import { RapierRigidBody, UseImpulseJoint, FixedJointParams, SphericalJointParams, RevoluteJointParams, PrismaticJointParams } from "..";
1
+ import { FixedImpulseJoint, ImpulseJoint, PrismaticImpulseJoint, RevoluteImpulseJoint, RopeImpulseJoint, SphericalImpulseJoint, SpringImpulseJoint } from "@dimforge/rapier3d-compat";
2
+ import { RefObject } from "react";
3
+ import { FixedJointParams, PrismaticJointParams, RapierRigidBody, RevoluteJointParams, RopeJointParams, SphericalJointParams, SpringJointParams, UseImpulseJoint } from "..";
4
4
  import type Rapier from "@dimforge/rapier3d-compat";
5
5
  /**
6
6
  * @internal
7
7
  */
8
- export declare const useImpulseJoint: <JointType extends ImpulseJoint>(body1: RefObject<RapierRigidBody>, body2: RefObject<RapierRigidBody>, params: Rapier.JointData) => React.MutableRefObject<JointType | undefined>;
8
+ export declare const useImpulseJoint: <JointType extends ImpulseJoint>(body1: RefObject<RapierRigidBody>, body2: RefObject<RapierRigidBody>, params: Rapier.JointData) => import("react").MutableRefObject<JointType | undefined>;
9
9
  /**
10
10
  * A fixed joint ensures that two rigid-bodies don't move relative to each other.
11
11
  * Fixed joints are characterized by one local frame (represented by an isometry) on each rigid-body.
@@ -39,3 +39,13 @@ export declare const useRevoluteJoint: UseImpulseJoint<RevoluteJointParams, Revo
39
39
  * @category Hooks - Joints
40
40
  */
41
41
  export declare const usePrismaticJoint: UseImpulseJoint<PrismaticJointParams, PrismaticImpulseJoint>;
42
+ /**
43
+ * The rope joint limits the max distance between two bodies.
44
+ * @category Hooks - Joints
45
+ */
46
+ export declare const useRopeJoint: UseImpulseJoint<RopeJointParams, RopeImpulseJoint>;
47
+ /**
48
+ * The spring joint applies a force proportional to the distance between two objects.
49
+ * @category Hooks - Joints
50
+ */
51
+ export declare const useSpringJoint: UseImpulseJoint<SpringJointParams, SpringImpulseJoint>;
@@ -1,7 +1,7 @@
1
1
  import { MutableRefObject, RefObject } from "react";
2
2
  import { CoefficientCombineRule, Collider as RapierCollider, ImpulseJoint, InteractionGroups, RigidBody as RapierRigidBody, TempContactManifold } from "@dimforge/rapier3d-compat";
3
3
  import { Rotation, Vector } from "@dimforge/rapier3d-compat/math";
4
- import { Object3DProps } from "@react-three/fiber";
4
+ import { Object3DProps, Vector3, Quaternion } from "@react-three/fiber";
5
5
  import { Object3D } from "three";
6
6
  import { ColliderProps } from ".";
7
7
  import { RigidBodyState } from "./components/Physics";
@@ -305,6 +305,16 @@ export interface RigidBodyOptions extends ColliderProps {
305
305
  * This does not affect any non-automatic child collider-components.
306
306
  */
307
307
  restitution?: number;
308
+ /**
309
+ * Sets the number of additional solver iterations that will be run for this
310
+ * rigid-body and everything that interacts with it directly or indirectly
311
+ * through contacts or joints.
312
+ *
313
+ * Compared to increasing the global `World.numSolverIteration`, setting this
314
+ * value lets you increase accuracy on only a subset of the scene, resulting in reduced
315
+ * performance loss.
316
+ */
317
+ additionalSolverIterations?: number;
308
318
  /**
309
319
  * The default collision groups bitmask for all colliders in this rigid body.
310
320
  * Can be customized per-collider.
@@ -348,27 +358,39 @@ export interface RigidBodyOptions extends ColliderProps {
348
358
  transformState?: (state: RigidBodyState) => RigidBodyState;
349
359
  }
350
360
  export declare type SphericalJointParams = [
351
- body1Anchor: Vector3Tuple,
352
- body2Anchor: Vector3Tuple
361
+ body1Anchor: Vector3,
362
+ body2Anchor: Vector3
353
363
  ];
354
364
  export declare type FixedJointParams = [
355
- body1Anchor: Vector3Tuple,
356
- body1LocalFrame: Vector4Tuple,
357
- body2Anchor: Vector3Tuple,
358
- body2LocalFrame: Vector4Tuple
365
+ body1Anchor: Vector3,
366
+ body1LocalFrame: Quaternion,
367
+ body2Anchor: Vector3,
368
+ body2LocalFrame: Quaternion
359
369
  ];
360
370
  export declare type PrismaticJointParams = [
361
- body1Anchor: Vector3Tuple,
362
- body2Anchor: Vector3Tuple,
363
- axis: Vector3Tuple,
371
+ body1Anchor: Vector3,
372
+ body2Anchor: Vector3,
373
+ axis: Vector3,
364
374
  limits?: [min: number, max: number]
365
375
  ];
366
376
  export declare type RevoluteJointParams = [
367
- body1Anchor: Vector3Tuple,
368
- body2Anchor: Vector3Tuple,
369
- axis: Vector3Tuple,
377
+ body1Anchor: Vector3,
378
+ body2Anchor: Vector3,
379
+ axis: Vector3,
370
380
  limits?: [min: number, max: number]
371
381
  ];
382
+ export declare type RopeJointParams = [
383
+ body1Anchor: Vector3,
384
+ body2Anchor: Vector3,
385
+ length: number
386
+ ];
387
+ export declare type SpringJointParams = [
388
+ body1Anchor: Vector3,
389
+ body2Anchor: Vector3,
390
+ restLength: number,
391
+ stiffness: number,
392
+ damping: number
393
+ ];
372
394
  export interface UseImpulseJoint<JointParams, JointType extends ImpulseJoint> {
373
395
  (body1: RefObject<RapierRigidBody>, body2: RefObject<RapierRigidBody>, params: JointParams): RefObject<JointType | undefined>;
374
396
  }
@@ -47,6 +47,7 @@ export declare const cleanRigidBodyPropsForCollider: (props?: RigidBodyProps) =>
47
47
  colliders?: RigidBodyAutoCollider | undefined;
48
48
  friction?: number | undefined;
49
49
  restitution?: number | undefined;
50
+ additionalSolverIterations?: number | undefined;
50
51
  collisionGroups?: number | undefined;
51
52
  solverGroups?: number | undefined;
52
53
  onSleep?(): void;
@@ -1,11 +1,13 @@
1
1
  import { Quaternion as RapierQuaternion, Vector3 as RapierVector3 } from "@dimforge/rapier3d-compat";
2
2
  import { Euler, Quaternion, Vector3 } from "three";
3
3
  import { RigidBodyTypeString, Vector3Tuple } from "../types";
4
+ import { Vector3 as Vector3Like, Quaternion as QuaternionLike } from "@react-three/fiber";
4
5
  export declare const vectorArrayToVector3: (arr: Vector3Tuple) => Vector3;
5
- export declare const tupleToObject: <T extends readonly any[], K extends readonly string[]>(tuple: T, keys: K) => { [Key in K[number]]: T[number]; };
6
6
  export declare const vector3ToQuaternion: (v: Vector3) => Quaternion;
7
7
  export declare const rapierVector3ToVector3: ({ x, y, z }: RapierVector3) => Vector3;
8
8
  export declare const rapierQuaternionToQuaternion: ({ x, y, z, w }: RapierQuaternion) => Quaternion;
9
+ export declare const vector3ToRapierVector: (v: Vector3Like) => RapierVector3;
10
+ export declare const quaternionToRapierQuaternion: (v: QuaternionLike) => RapierQuaternion;
9
11
  export declare const rigidBodyTypeFromString: (type: RigidBodyTypeString) => 0 | 3 | 1 | 2;
10
12
  export declare const scaleVertices: (vertices: ArrayLike<number>, scale: Vector3) => number[];
11
13
  export declare const vectorToTuple: (v: Vector3 | Quaternion | any[] | undefined | number | Euler) => any[];
@@ -85,18 +85,29 @@ const vectorArrayToVector3 = arr => {
85
85
  const [x, y, z] = arr;
86
86
  return new three.Vector3(x, y, z);
87
87
  };
88
- const tupleToObject = (tuple, keys) => {
89
- return keys.reduce((obj, key, i) => {
90
- obj[key] = tuple[i];
91
- return obj;
92
- }, {});
93
- };
94
88
  const rapierQuaternionToQuaternion = ({
95
89
  x,
96
90
  y,
97
91
  z,
98
92
  w
99
93
  }) => _quaternion.set(x, y, z, w);
94
+ const vector3ToRapierVector = v => {
95
+ if (Array.isArray(v)) {
96
+ return new rapier3dCompat.Vector3(v[0], v[1], v[2]);
97
+ } else if (typeof v === "number") {
98
+ return new rapier3dCompat.Vector3(v, v, v);
99
+ } else {
100
+ const threeVector3 = v;
101
+ return new rapier3dCompat.Vector3(threeVector3.x, threeVector3.y, threeVector3.z);
102
+ }
103
+ };
104
+ const quaternionToRapierQuaternion = v => {
105
+ if (Array.isArray(v)) {
106
+ return new rapier3dCompat.Quaternion(v[0], v[1], v[2], v[3]);
107
+ } else {
108
+ return new rapier3dCompat.Quaternion(v.x, v.y, v.z, v.w);
109
+ }
110
+ };
100
111
  const rigidBodyTypeMap = {
101
112
  fixed: 1,
102
113
  dynamic: 0,
@@ -744,10 +755,13 @@ const Physics = props => {
744
755
  updateLoop = "follow",
745
756
  debug = false,
746
757
  gravity = [0, -9.81, 0],
747
- maxStabilizationIterations = 1,
748
- maxVelocityFrictionIterations = 8,
749
- maxVelocityIterations = 4,
758
+ allowedLinearError = 0.001,
750
759
  predictionDistance = 0.002,
760
+ numSolverIterations = 4,
761
+ numAdditionalFrictionIterations = 4,
762
+ numInternalPgsIterations = 1,
763
+ minIslandSize = 128,
764
+ maxCcdSubsteps = 1,
751
765
  erp = 0.8
752
766
  } = props;
753
767
  const rapier = useAsset.useAsset(importRapier);
@@ -780,13 +794,16 @@ const Physics = props => {
780
794
  }, []); // Update mutable props
781
795
 
782
796
  React.useEffect(() => {
783
- worldProxy.gravity = vectorArrayToVector3(gravity);
784
- worldProxy.integrationParameters.maxStabilizationIterations = maxStabilizationIterations;
785
- worldProxy.integrationParameters.maxVelocityFrictionIterations = maxVelocityFrictionIterations;
786
- worldProxy.integrationParameters.maxVelocityIterations = maxVelocityIterations;
797
+ worldProxy.gravity = vector3ToRapierVector(gravity);
798
+ worldProxy.integrationParameters.numSolverIterations = numSolverIterations;
799
+ worldProxy.integrationParameters.numAdditionalFrictionIterations = numAdditionalFrictionIterations;
800
+ worldProxy.integrationParameters.numInternalPgsIterations = numInternalPgsIterations;
801
+ worldProxy.integrationParameters.allowedLinearError = allowedLinearError;
802
+ worldProxy.integrationParameters.minIslandSize = minIslandSize;
803
+ worldProxy.integrationParameters.maxCcdSubsteps = maxCcdSubsteps;
787
804
  worldProxy.integrationParameters.predictionDistance = predictionDistance;
788
805
  worldProxy.integrationParameters.erp = erp;
789
- }, [worldProxy, ...gravity, maxStabilizationIterations, maxVelocityIterations, maxVelocityFrictionIterations, predictionDistance, erp]);
806
+ }, [worldProxy, ...gravity, numSolverIterations, numAdditionalFrictionIterations, numInternalPgsIterations, allowedLinearError, minIslandSize, maxCcdSubsteps, predictionDistance, erp]);
790
807
  const getSourceFromColliderHandle = React.useCallback(handle => {
791
808
  var _collider$parent;
792
809
 
@@ -1375,6 +1392,11 @@ const mutableRigidBodyOptions = {
1375
1392
  gravityScale: (rb, value) => {
1376
1393
  rb.setGravityScale(value, true);
1377
1394
  },
1395
+
1396
+ additionalSolverIterations(rb, value) {
1397
+ rb.setAdditionalSolverIterations(value);
1398
+ },
1399
+
1378
1400
  linearDamping: (rb, value) => {
1379
1401
  rb.setLinearDamping(value);
1380
1402
  },
@@ -1733,7 +1755,7 @@ const useFixedJoint = (body1, body2, [body1Anchor, body1LocalFrame, body2Anchor,
1733
1755
  const {
1734
1756
  rapier
1735
1757
  } = useRapier();
1736
- return useImpulseJoint(body1, body2, rapier.JointData.fixed(vectorArrayToVector3(body1Anchor), tupleToObject(body1LocalFrame, ["x", "y", "z", "w"]), vectorArrayToVector3(body2Anchor), tupleToObject(body2LocalFrame, ["x", "y", "z", "w"])));
1758
+ return useImpulseJoint(body1, body2, rapier.JointData.fixed(vector3ToRapierVector(body1Anchor), quaternionToRapierQuaternion(body1LocalFrame), vector3ToRapierVector(body2Anchor), quaternionToRapierQuaternion(body2LocalFrame)));
1737
1759
  };
1738
1760
  /**
1739
1761
  * The spherical joint ensures that two points on the local-spaces of two rigid-bodies always coincide (it prevents any relative
@@ -1748,7 +1770,7 @@ const useSphericalJoint = (body1, body2, [body1Anchor, body2Anchor]) => {
1748
1770
  const {
1749
1771
  rapier
1750
1772
  } = useRapier();
1751
- return useImpulseJoint(body1, body2, rapier.JointData.spherical(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor)));
1773
+ return useImpulseJoint(body1, body2, rapier.JointData.spherical(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor)));
1752
1774
  };
1753
1775
  /**
1754
1776
  * The revolute joint prevents any relative movement between two rigid-bodies, except for relative
@@ -1762,7 +1784,7 @@ const useRevoluteJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits]
1762
1784
  const {
1763
1785
  rapier
1764
1786
  } = useRapier();
1765
- const params = rapier.JointData.revolute(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis));
1787
+ const params = rapier.JointData.revolute(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor), vector3ToRapierVector(axis));
1766
1788
 
1767
1789
  if (limits) {
1768
1790
  params.limitsEnabled = true;
@@ -1783,7 +1805,7 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits
1783
1805
  const {
1784
1806
  rapier
1785
1807
  } = useRapier();
1786
- const params = rapier.JointData.prismatic(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis));
1808
+ const params = rapier.JointData.prismatic(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor), vector3ToRapierVector(axis));
1787
1809
 
1788
1810
  if (limits) {
1789
1811
  params.limitsEnabled = true;
@@ -1792,6 +1814,34 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits
1792
1814
 
1793
1815
  return useImpulseJoint(body1, body2, params);
1794
1816
  };
1817
+ /**
1818
+ * The rope joint limits the max distance between two bodies.
1819
+ * @category Hooks - Joints
1820
+ */
1821
+
1822
+ const useRopeJoint = (body1, body2, [body1Anchor, body2Anchor, length]) => {
1823
+ const {
1824
+ rapier
1825
+ } = useRapier();
1826
+ const vBody1Anchor = vector3ToRapierVector(body1Anchor);
1827
+ const vBody2Anchor = vector3ToRapierVector(body2Anchor);
1828
+ const params = rapier.JointData.rope(length, vBody1Anchor, vBody2Anchor);
1829
+ return useImpulseJoint(body1, body2, params);
1830
+ };
1831
+ /**
1832
+ * The spring joint applies a force proportional to the distance between two objects.
1833
+ * @category Hooks - Joints
1834
+ */
1835
+
1836
+ const useSpringJoint = (body1, body2, [body1Anchor, body2Anchor, restLength, stiffness, damping]) => {
1837
+ const {
1838
+ rapier
1839
+ } = useRapier();
1840
+ const vBody1Anchor = vector3ToRapierVector(body1Anchor);
1841
+ const vBody2Anchor = vector3ToRapierVector(body2Anchor);
1842
+ const params = rapier.JointData.spring(restLength, stiffness, damping, vBody1Anchor, vBody2Anchor);
1843
+ return useImpulseJoint(body1, body2, params);
1844
+ };
1795
1845
 
1796
1846
  /**
1797
1847
  * Calculates an InteractionGroup bitmask for use in the `collisionGroups` or `solverGroups`
@@ -1867,5 +1917,7 @@ exports.useImpulseJoint = useImpulseJoint;
1867
1917
  exports.usePrismaticJoint = usePrismaticJoint;
1868
1918
  exports.useRapier = useRapier;
1869
1919
  exports.useRevoluteJoint = useRevoluteJoint;
1920
+ exports.useRopeJoint = useRopeJoint;
1870
1921
  exports.useSphericalJoint = useSphericalJoint;
1922
+ exports.useSpringJoint = useSpringJoint;
1871
1923
  exports.vec3 = vec3;
@@ -85,18 +85,29 @@ const vectorArrayToVector3 = arr => {
85
85
  const [x, y, z] = arr;
86
86
  return new three.Vector3(x, y, z);
87
87
  };
88
- const tupleToObject = (tuple, keys) => {
89
- return keys.reduce((obj, key, i) => {
90
- obj[key] = tuple[i];
91
- return obj;
92
- }, {});
93
- };
94
88
  const rapierQuaternionToQuaternion = ({
95
89
  x,
96
90
  y,
97
91
  z,
98
92
  w
99
93
  }) => _quaternion.set(x, y, z, w);
94
+ const vector3ToRapierVector = v => {
95
+ if (Array.isArray(v)) {
96
+ return new rapier3dCompat.Vector3(v[0], v[1], v[2]);
97
+ } else if (typeof v === "number") {
98
+ return new rapier3dCompat.Vector3(v, v, v);
99
+ } else {
100
+ const threeVector3 = v;
101
+ return new rapier3dCompat.Vector3(threeVector3.x, threeVector3.y, threeVector3.z);
102
+ }
103
+ };
104
+ const quaternionToRapierQuaternion = v => {
105
+ if (Array.isArray(v)) {
106
+ return new rapier3dCompat.Quaternion(v[0], v[1], v[2], v[3]);
107
+ } else {
108
+ return new rapier3dCompat.Quaternion(v.x, v.y, v.z, v.w);
109
+ }
110
+ };
100
111
  const rigidBodyTypeMap = {
101
112
  fixed: 1,
102
113
  dynamic: 0,
@@ -744,10 +755,13 @@ const Physics = props => {
744
755
  updateLoop = "follow",
745
756
  debug = false,
746
757
  gravity = [0, -9.81, 0],
747
- maxStabilizationIterations = 1,
748
- maxVelocityFrictionIterations = 8,
749
- maxVelocityIterations = 4,
758
+ allowedLinearError = 0.001,
750
759
  predictionDistance = 0.002,
760
+ numSolverIterations = 4,
761
+ numAdditionalFrictionIterations = 4,
762
+ numInternalPgsIterations = 1,
763
+ minIslandSize = 128,
764
+ maxCcdSubsteps = 1,
751
765
  erp = 0.8
752
766
  } = props;
753
767
  const rapier = useAsset.useAsset(importRapier);
@@ -780,13 +794,16 @@ const Physics = props => {
780
794
  }, []); // Update mutable props
781
795
 
782
796
  React.useEffect(() => {
783
- worldProxy.gravity = vectorArrayToVector3(gravity);
784
- worldProxy.integrationParameters.maxStabilizationIterations = maxStabilizationIterations;
785
- worldProxy.integrationParameters.maxVelocityFrictionIterations = maxVelocityFrictionIterations;
786
- worldProxy.integrationParameters.maxVelocityIterations = maxVelocityIterations;
797
+ worldProxy.gravity = vector3ToRapierVector(gravity);
798
+ worldProxy.integrationParameters.numSolverIterations = numSolverIterations;
799
+ worldProxy.integrationParameters.numAdditionalFrictionIterations = numAdditionalFrictionIterations;
800
+ worldProxy.integrationParameters.numInternalPgsIterations = numInternalPgsIterations;
801
+ worldProxy.integrationParameters.allowedLinearError = allowedLinearError;
802
+ worldProxy.integrationParameters.minIslandSize = minIslandSize;
803
+ worldProxy.integrationParameters.maxCcdSubsteps = maxCcdSubsteps;
787
804
  worldProxy.integrationParameters.predictionDistance = predictionDistance;
788
805
  worldProxy.integrationParameters.erp = erp;
789
- }, [worldProxy, ...gravity, maxStabilizationIterations, maxVelocityIterations, maxVelocityFrictionIterations, predictionDistance, erp]);
806
+ }, [worldProxy, ...gravity, numSolverIterations, numAdditionalFrictionIterations, numInternalPgsIterations, allowedLinearError, minIslandSize, maxCcdSubsteps, predictionDistance, erp]);
790
807
  const getSourceFromColliderHandle = React.useCallback(handle => {
791
808
  var _collider$parent;
792
809
 
@@ -1375,6 +1392,11 @@ const mutableRigidBodyOptions = {
1375
1392
  gravityScale: (rb, value) => {
1376
1393
  rb.setGravityScale(value, true);
1377
1394
  },
1395
+
1396
+ additionalSolverIterations(rb, value) {
1397
+ rb.setAdditionalSolverIterations(value);
1398
+ },
1399
+
1378
1400
  linearDamping: (rb, value) => {
1379
1401
  rb.setLinearDamping(value);
1380
1402
  },
@@ -1733,7 +1755,7 @@ const useFixedJoint = (body1, body2, [body1Anchor, body1LocalFrame, body2Anchor,
1733
1755
  const {
1734
1756
  rapier
1735
1757
  } = useRapier();
1736
- return useImpulseJoint(body1, body2, rapier.JointData.fixed(vectorArrayToVector3(body1Anchor), tupleToObject(body1LocalFrame, ["x", "y", "z", "w"]), vectorArrayToVector3(body2Anchor), tupleToObject(body2LocalFrame, ["x", "y", "z", "w"])));
1758
+ return useImpulseJoint(body1, body2, rapier.JointData.fixed(vector3ToRapierVector(body1Anchor), quaternionToRapierQuaternion(body1LocalFrame), vector3ToRapierVector(body2Anchor), quaternionToRapierQuaternion(body2LocalFrame)));
1737
1759
  };
1738
1760
  /**
1739
1761
  * The spherical joint ensures that two points on the local-spaces of two rigid-bodies always coincide (it prevents any relative
@@ -1748,7 +1770,7 @@ const useSphericalJoint = (body1, body2, [body1Anchor, body2Anchor]) => {
1748
1770
  const {
1749
1771
  rapier
1750
1772
  } = useRapier();
1751
- return useImpulseJoint(body1, body2, rapier.JointData.spherical(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor)));
1773
+ return useImpulseJoint(body1, body2, rapier.JointData.spherical(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor)));
1752
1774
  };
1753
1775
  /**
1754
1776
  * The revolute joint prevents any relative movement between two rigid-bodies, except for relative
@@ -1762,7 +1784,7 @@ const useRevoluteJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits]
1762
1784
  const {
1763
1785
  rapier
1764
1786
  } = useRapier();
1765
- const params = rapier.JointData.revolute(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis));
1787
+ const params = rapier.JointData.revolute(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor), vector3ToRapierVector(axis));
1766
1788
 
1767
1789
  if (limits) {
1768
1790
  params.limitsEnabled = true;
@@ -1783,7 +1805,7 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits
1783
1805
  const {
1784
1806
  rapier
1785
1807
  } = useRapier();
1786
- const params = rapier.JointData.prismatic(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis));
1808
+ const params = rapier.JointData.prismatic(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor), vector3ToRapierVector(axis));
1787
1809
 
1788
1810
  if (limits) {
1789
1811
  params.limitsEnabled = true;
@@ -1792,6 +1814,34 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits
1792
1814
 
1793
1815
  return useImpulseJoint(body1, body2, params);
1794
1816
  };
1817
+ /**
1818
+ * The rope joint limits the max distance between two bodies.
1819
+ * @category Hooks - Joints
1820
+ */
1821
+
1822
+ const useRopeJoint = (body1, body2, [body1Anchor, body2Anchor, length]) => {
1823
+ const {
1824
+ rapier
1825
+ } = useRapier();
1826
+ const vBody1Anchor = vector3ToRapierVector(body1Anchor);
1827
+ const vBody2Anchor = vector3ToRapierVector(body2Anchor);
1828
+ const params = rapier.JointData.rope(length, vBody1Anchor, vBody2Anchor);
1829
+ return useImpulseJoint(body1, body2, params);
1830
+ };
1831
+ /**
1832
+ * The spring joint applies a force proportional to the distance between two objects.
1833
+ * @category Hooks - Joints
1834
+ */
1835
+
1836
+ const useSpringJoint = (body1, body2, [body1Anchor, body2Anchor, restLength, stiffness, damping]) => {
1837
+ const {
1838
+ rapier
1839
+ } = useRapier();
1840
+ const vBody1Anchor = vector3ToRapierVector(body1Anchor);
1841
+ const vBody2Anchor = vector3ToRapierVector(body2Anchor);
1842
+ const params = rapier.JointData.spring(restLength, stiffness, damping, vBody1Anchor, vBody2Anchor);
1843
+ return useImpulseJoint(body1, body2, params);
1844
+ };
1795
1845
 
1796
1846
  /**
1797
1847
  * Calculates an InteractionGroup bitmask for use in the `collisionGroups` or `solverGroups`
@@ -1867,5 +1917,7 @@ exports.useImpulseJoint = useImpulseJoint;
1867
1917
  exports.usePrismaticJoint = usePrismaticJoint;
1868
1918
  exports.useRapier = useRapier;
1869
1919
  exports.useRevoluteJoint = useRevoluteJoint;
1920
+ exports.useRopeJoint = useRopeJoint;
1870
1921
  exports.useSphericalJoint = useSphericalJoint;
1922
+ exports.useSpringJoint = useSpringJoint;
1871
1923
  exports.vec3 = vec3;
@@ -1,4 +1,4 @@
1
- import { ActiveEvents, ColliderDesc, EventQueue, RigidBodyDesc } from '@dimforge/rapier3d-compat';
1
+ import { Vector3 as Vector3$1, Quaternion as Quaternion$1, ActiveEvents, ColliderDesc, EventQueue, RigidBodyDesc } from '@dimforge/rapier3d-compat';
2
2
  export { CoefficientCombineRule, Collider as RapierCollider, RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat';
3
3
  import { useFrame, useThree } from '@react-three/fiber';
4
4
  import React, { useRef, useEffect, memo, useMemo, useContext, useState, createContext, useCallback, forwardRef, Fragment } from 'react';
@@ -60,18 +60,29 @@ const vectorArrayToVector3 = arr => {
60
60
  const [x, y, z] = arr;
61
61
  return new Vector3(x, y, z);
62
62
  };
63
- const tupleToObject = (tuple, keys) => {
64
- return keys.reduce((obj, key, i) => {
65
- obj[key] = tuple[i];
66
- return obj;
67
- }, {});
68
- };
69
63
  const rapierQuaternionToQuaternion = ({
70
64
  x,
71
65
  y,
72
66
  z,
73
67
  w
74
68
  }) => _quaternion.set(x, y, z, w);
69
+ const vector3ToRapierVector = v => {
70
+ if (Array.isArray(v)) {
71
+ return new Vector3$1(v[0], v[1], v[2]);
72
+ } else if (typeof v === "number") {
73
+ return new Vector3$1(v, v, v);
74
+ } else {
75
+ const threeVector3 = v;
76
+ return new Vector3$1(threeVector3.x, threeVector3.y, threeVector3.z);
77
+ }
78
+ };
79
+ const quaternionToRapierQuaternion = v => {
80
+ if (Array.isArray(v)) {
81
+ return new Quaternion$1(v[0], v[1], v[2], v[3]);
82
+ } else {
83
+ return new Quaternion$1(v.x, v.y, v.z, v.w);
84
+ }
85
+ };
75
86
  const rigidBodyTypeMap = {
76
87
  fixed: 1,
77
88
  dynamic: 0,
@@ -719,10 +730,13 @@ const Physics = props => {
719
730
  updateLoop = "follow",
720
731
  debug = false,
721
732
  gravity = [0, -9.81, 0],
722
- maxStabilizationIterations = 1,
723
- maxVelocityFrictionIterations = 8,
724
- maxVelocityIterations = 4,
733
+ allowedLinearError = 0.001,
725
734
  predictionDistance = 0.002,
735
+ numSolverIterations = 4,
736
+ numAdditionalFrictionIterations = 4,
737
+ numInternalPgsIterations = 1,
738
+ minIslandSize = 128,
739
+ maxCcdSubsteps = 1,
726
740
  erp = 0.8
727
741
  } = props;
728
742
  const rapier = useAsset(importRapier);
@@ -755,13 +769,16 @@ const Physics = props => {
755
769
  }, []); // Update mutable props
756
770
 
757
771
  useEffect(() => {
758
- worldProxy.gravity = vectorArrayToVector3(gravity);
759
- worldProxy.integrationParameters.maxStabilizationIterations = maxStabilizationIterations;
760
- worldProxy.integrationParameters.maxVelocityFrictionIterations = maxVelocityFrictionIterations;
761
- worldProxy.integrationParameters.maxVelocityIterations = maxVelocityIterations;
772
+ worldProxy.gravity = vector3ToRapierVector(gravity);
773
+ worldProxy.integrationParameters.numSolverIterations = numSolverIterations;
774
+ worldProxy.integrationParameters.numAdditionalFrictionIterations = numAdditionalFrictionIterations;
775
+ worldProxy.integrationParameters.numInternalPgsIterations = numInternalPgsIterations;
776
+ worldProxy.integrationParameters.allowedLinearError = allowedLinearError;
777
+ worldProxy.integrationParameters.minIslandSize = minIslandSize;
778
+ worldProxy.integrationParameters.maxCcdSubsteps = maxCcdSubsteps;
762
779
  worldProxy.integrationParameters.predictionDistance = predictionDistance;
763
780
  worldProxy.integrationParameters.erp = erp;
764
- }, [worldProxy, ...gravity, maxStabilizationIterations, maxVelocityIterations, maxVelocityFrictionIterations, predictionDistance, erp]);
781
+ }, [worldProxy, ...gravity, numSolverIterations, numAdditionalFrictionIterations, numInternalPgsIterations, allowedLinearError, minIslandSize, maxCcdSubsteps, predictionDistance, erp]);
765
782
  const getSourceFromColliderHandle = useCallback(handle => {
766
783
  var _collider$parent;
767
784
 
@@ -1350,6 +1367,11 @@ const mutableRigidBodyOptions = {
1350
1367
  gravityScale: (rb, value) => {
1351
1368
  rb.setGravityScale(value, true);
1352
1369
  },
1370
+
1371
+ additionalSolverIterations(rb, value) {
1372
+ rb.setAdditionalSolverIterations(value);
1373
+ },
1374
+
1353
1375
  linearDamping: (rb, value) => {
1354
1376
  rb.setLinearDamping(value);
1355
1377
  },
@@ -1708,7 +1730,7 @@ const useFixedJoint = (body1, body2, [body1Anchor, body1LocalFrame, body2Anchor,
1708
1730
  const {
1709
1731
  rapier
1710
1732
  } = useRapier();
1711
- return useImpulseJoint(body1, body2, rapier.JointData.fixed(vectorArrayToVector3(body1Anchor), tupleToObject(body1LocalFrame, ["x", "y", "z", "w"]), vectorArrayToVector3(body2Anchor), tupleToObject(body2LocalFrame, ["x", "y", "z", "w"])));
1733
+ return useImpulseJoint(body1, body2, rapier.JointData.fixed(vector3ToRapierVector(body1Anchor), quaternionToRapierQuaternion(body1LocalFrame), vector3ToRapierVector(body2Anchor), quaternionToRapierQuaternion(body2LocalFrame)));
1712
1734
  };
1713
1735
  /**
1714
1736
  * The spherical joint ensures that two points on the local-spaces of two rigid-bodies always coincide (it prevents any relative
@@ -1723,7 +1745,7 @@ const useSphericalJoint = (body1, body2, [body1Anchor, body2Anchor]) => {
1723
1745
  const {
1724
1746
  rapier
1725
1747
  } = useRapier();
1726
- return useImpulseJoint(body1, body2, rapier.JointData.spherical(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor)));
1748
+ return useImpulseJoint(body1, body2, rapier.JointData.spherical(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor)));
1727
1749
  };
1728
1750
  /**
1729
1751
  * The revolute joint prevents any relative movement between two rigid-bodies, except for relative
@@ -1737,7 +1759,7 @@ const useRevoluteJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits]
1737
1759
  const {
1738
1760
  rapier
1739
1761
  } = useRapier();
1740
- const params = rapier.JointData.revolute(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis));
1762
+ const params = rapier.JointData.revolute(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor), vector3ToRapierVector(axis));
1741
1763
 
1742
1764
  if (limits) {
1743
1765
  params.limitsEnabled = true;
@@ -1758,7 +1780,7 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits
1758
1780
  const {
1759
1781
  rapier
1760
1782
  } = useRapier();
1761
- const params = rapier.JointData.prismatic(vectorArrayToVector3(body1Anchor), vectorArrayToVector3(body2Anchor), vectorArrayToVector3(axis));
1783
+ const params = rapier.JointData.prismatic(vector3ToRapierVector(body1Anchor), vector3ToRapierVector(body2Anchor), vector3ToRapierVector(axis));
1762
1784
 
1763
1785
  if (limits) {
1764
1786
  params.limitsEnabled = true;
@@ -1767,6 +1789,34 @@ const usePrismaticJoint = (body1, body2, [body1Anchor, body2Anchor, axis, limits
1767
1789
 
1768
1790
  return useImpulseJoint(body1, body2, params);
1769
1791
  };
1792
+ /**
1793
+ * The rope joint limits the max distance between two bodies.
1794
+ * @category Hooks - Joints
1795
+ */
1796
+
1797
+ const useRopeJoint = (body1, body2, [body1Anchor, body2Anchor, length]) => {
1798
+ const {
1799
+ rapier
1800
+ } = useRapier();
1801
+ const vBody1Anchor = vector3ToRapierVector(body1Anchor);
1802
+ const vBody2Anchor = vector3ToRapierVector(body2Anchor);
1803
+ const params = rapier.JointData.rope(length, vBody1Anchor, vBody2Anchor);
1804
+ return useImpulseJoint(body1, body2, params);
1805
+ };
1806
+ /**
1807
+ * The spring joint applies a force proportional to the distance between two objects.
1808
+ * @category Hooks - Joints
1809
+ */
1810
+
1811
+ const useSpringJoint = (body1, body2, [body1Anchor, body2Anchor, restLength, stiffness, damping]) => {
1812
+ const {
1813
+ rapier
1814
+ } = useRapier();
1815
+ const vBody1Anchor = vector3ToRapierVector(body1Anchor);
1816
+ const vBody2Anchor = vector3ToRapierVector(body2Anchor);
1817
+ const params = rapier.JointData.spring(restLength, stiffness, damping, vBody1Anchor, vBody2Anchor);
1818
+ return useImpulseJoint(body1, body2, params);
1819
+ };
1770
1820
 
1771
1821
  /**
1772
1822
  * Calculates an InteractionGroup bitmask for use in the `collisionGroups` or `solverGroups`
@@ -1804,4 +1854,4 @@ const interactionGroups = (memberships, filters) => (bitmask(memberships) << 16)
1804
1854
 
1805
1855
  const bitmask = groups => [groups].flat().reduce((acc, layer) => acc | 1 << layer, 0);
1806
1856
 
1807
- export { AnyCollider, BallCollider, CapsuleCollider, ConeCollider, ConvexHullCollider, CuboidCollider, CylinderCollider, HeightfieldCollider, InstancedRigidBodies, MeshCollider, Physics, RigidBody, RoundConeCollider, RoundCuboidCollider, RoundCylinderCollider, TrimeshCollider, euler, interactionGroups, quat, useAfterPhysicsStep, useBeforePhysicsStep, useFixedJoint, useImpulseJoint, usePrismaticJoint, useRapier, useRevoluteJoint, useSphericalJoint, vec3 };
1857
+ export { AnyCollider, BallCollider, CapsuleCollider, ConeCollider, ConvexHullCollider, CuboidCollider, CylinderCollider, HeightfieldCollider, InstancedRigidBodies, MeshCollider, Physics, RigidBody, RoundConeCollider, RoundCuboidCollider, RoundCylinderCollider, TrimeshCollider, euler, interactionGroups, quat, useAfterPhysicsStep, useBeforePhysicsStep, useFixedJoint, useImpulseJoint, usePrismaticJoint, useRapier, useRevoluteJoint, useRopeJoint, useSphericalJoint, useSpringJoint, vec3 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/rapier",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "source": "src/index.ts",
5
5
  "main": "dist/react-three-rapier.cjs.js",
6
6
  "module": "dist/react-three-rapier.esm.js",
@@ -29,7 +29,7 @@
29
29
  "three": ">=0.139.0"
30
30
  },
31
31
  "dependencies": {
32
- "@dimforge/rapier3d-compat": "0.11.2",
32
+ "@dimforge/rapier3d-compat": "0.12.0",
33
33
  "three-stdlib": "2.23.9",
34
34
  "use-asset": "1.0.4"
35
35
  },
package/readme.md CHANGED
@@ -78,6 +78,8 @@ For full API outline and documentation, see 🧩 [API Docs](https://pmndrs.githu
78
78
  - [Spherical Joint](#spherical-joint)
79
79
  - [Revolute Joint](#revolute-joint)
80
80
  - [Prismatic Joint](#prismatic-joint)
81
+ - [Rope Joint](#rope-joint)
82
+ - [Spring Joint](#spring-joint)
81
83
  - [🖼 Joints Example](#-joints-example)
82
84
  - [Advanced hooks usage](#advanced-hooks-usage)
83
85
  - [Manual stepping](#manual-stepping)
@@ -623,12 +625,14 @@ Joints can be made between two `RigidBodies` to provide a way to restrict a moti
623
625
 
624
626
  Joints are available in `r3/rapier` as hooks.
625
627
 
626
- There are 4 different joint types available:
628
+ There are 6 different joint types available:
627
629
 
628
630
  - Fixed (two bodies are fixed together)
629
631
  - Spherical (two bodies are connected by a ball and socket, for things like arms or chains)
630
632
  - Revolute (two bodies are connected by a hinge, for things like doors or wheels)
631
633
  - Prismatic (two bodies are connected by a sliding joint, for things like pistons or sliders)
634
+ - Rope (limits the max distance between two bodies)
635
+ - Spring (applies a force proportional to the distance between two bodies)
632
636
 
633
637
  Each joint hook returns a RefObject containing the raw reference to the joint instance.
634
638
 
@@ -658,6 +662,9 @@ A fixed joint ensures that two rigid-bodies don't move relative to each other. F
658
662
 
659
663
  ```tsx
660
664
  const JointedThing = () => {
665
+ const bodyA = useRef<RapierRigidBody>(null);
666
+ const bodyB = useRef<RapierRigidBody>(null);
667
+
661
668
  const joint = useFixedJoint(bodyA, bodyB, [
662
669
  // Position of the joint in bodyA's local space
663
670
  [0, 0, 0],
@@ -690,6 +697,9 @@ The spherical joint ensures that two points on the local-spaces of two rigid-bod
690
697
 
691
698
  ```tsx
692
699
  const JointedThing = () => {
700
+ const bodyA = useRef<RapierRigidBody>(null);
701
+ const bodyB = useRef<RapierRigidBody>(null);
702
+
693
703
  const joint = useSphericalJoint(bodyA, bodyB, [
694
704
  // Position of the joint in bodyA's local space
695
705
  [0, 0, 0],
@@ -718,6 +728,9 @@ The revolute joint prevents any relative movement between two rigid-bodies, exce
718
728
 
719
729
  ```tsx
720
730
  const JointedThing = () => {
731
+ const bodyA = useRef<RapierRigidBody>(null);
732
+ const bodyB = useRef<RapierRigidBody>(null);
733
+
721
734
  const joint = useRevoluteJoint(bodyA, bodyB, [
722
735
  // Position of the joint in bodyA's local space
723
736
  [0, 0, 0],
@@ -749,6 +762,9 @@ The prismatic joint prevents any relative movement between two rigid-bodies, exc
749
762
 
750
763
  ```tsx
751
764
  const JointedThing = () => {
765
+ const bodyA = useRef<RapierRigidBody>(null);
766
+ const bodyB = useRef<RapierRigidBody>(null);
767
+
752
768
  const joint = usePrismaticJoint(bodyA, bodyB, [
753
769
  // Position of the joint in bodyA's local space
754
770
  [0, 0, 0],
@@ -772,6 +788,83 @@ const JointedThing = () => {
772
788
  };
773
789
  ```
774
790
 
791
+ ### Rope Joint
792
+
793
+ The rope joint limits the max distance between two bodies.
794
+
795
+ 🧩 See [RopeJoint docs](https://pmndrs.github.io/react-three-rapier/functions/useRopeJoint.html) for available options.
796
+
797
+ ```tsx
798
+ const JointedThing = () => {
799
+ const bodyA = useRef<RapierRigidBody>(null);
800
+ const bodyB = useRef<RapierRigidBody>(null);
801
+
802
+ const joint = useRopeJoint(bodyA, bodyB, [
803
+ // Position of the joint in bodyA's local space
804
+ [0, 0, 0],
805
+ // Position of the joint in bodyB's local space
806
+ [0, 0, 0],
807
+ // The max distance between the two bodies / length of the rope
808
+ 1
809
+ ]);
810
+
811
+ return (
812
+ <group>
813
+ <RigidBody ref={bodyA}>
814
+ <mesh />
815
+ </RigidBody>
816
+ <RigidBody ref={bodyB}>
817
+ <mesh />
818
+ </RigidBody>
819
+ </group>
820
+ );
821
+ };
822
+ ```
823
+
824
+ ### Spring Joint
825
+
826
+ The spring joint applies a force proportional to the distance between two bodies.
827
+
828
+ 🧩 See [SpringJoint docs](https://pmndrs.github.io/react-three-rapier/functions/useSpringJoint.html) for available options.
829
+
830
+ ```tsx
831
+ const JointedThing = () => {
832
+ const bodyA = useRef<RapierRigidBody>(null);
833
+ const bodyB = useRef<RapierRigidBody>(null);
834
+
835
+ const mass = 1;
836
+ const springRestLength = 0;
837
+ const stiffness = 1.0e3;
838
+ const criticalDamping = 2.0 * Math.sqrt(stiffness * mass);
839
+ const dampingRatio = props.jointNum / (props.total / 2);
840
+ const damping = dampingRatio * criticalDamping;
841
+
842
+ const joint = useSpringJoint(bodyA, bodyB, [
843
+ // Position of the joint in bodyA's local space
844
+ [0, 0, 0],
845
+ // Position of the joint in bodyB's local space
846
+ [0, 0, 0],
847
+ // Spring rest length
848
+ springRestLength,
849
+ // Spring stiffness
850
+ stiffness,
851
+ // Spring damping
852
+ damping
853
+ ]);
854
+
855
+ return (
856
+ <group>
857
+ <RigidBody ref={bodyA}>
858
+ <mesh />
859
+ </RigidBody>
860
+ <RigidBody ref={bodyB}>
861
+ <mesh />
862
+ </RigidBody>
863
+ </group>
864
+ );
865
+ };
866
+ ```
867
+
775
868
  ### 🖼 Joints Example
776
869
 
777
870
  <a href="https://codesandbox.io/s/react-three-rapier-joints-mhhbd4"><img src="https://raw.githubusercontent.com/pmndrs/react-three-rapier/HEAD/packages/react-three-rapier/misc/example-joints.jpg" width="240" /></a>