diff --git a/.changeset/cold-apples-drop.md b/.changeset/cold-apples-drop.md new file mode 100644 index 00000000..f76e9aa0 --- /dev/null +++ b/.changeset/cold-apples-drop.md @@ -0,0 +1,12 @@ +--- +"@react-three/rapier": minor +--- + +feat: update @dimforge/rapier3d-compat to 0.12.0 (@0xtito, @isaac-mason) + +- Change Physics component props to match the new rapier version's integration parameter changes. + - There aren't direct alternatives for all old parameters. See the Physics component docs for more information on the new parameters: https://pmndrs.github.io/react-three-rapier/interfaces/PhysicsProps.html +- Add `additionalSolverIterations` prop to `RigidBodyOptions`. + - See: https://pmndrs.github.io/react-three-rapier/interfaces/RigidBodyOptions.html#additionalSolverIterations +- Add `useSpringJoint` +- Add `useRopeJoint` \ No newline at end of file diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..52595472 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,10 @@ +{ + "mode": "pre", + "tag": "canary", + "initialVersions": { + "demo": "0.0.0", + "@react-three/rapier": "1.2.1", + "@react-three/rapier-addons": "3.0.3" + }, + "changesets": [] +} diff --git a/demo/src/App.tsx b/demo/src/App.tsx index e4699111..824ef213 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -1,15 +1,14 @@ import { Box, Environment, OrbitControls } from "@react-three/drei"; import { Canvas } from "@react-three/fiber"; -import { Physics, RigidBody, useRapier } from "@react-three/rapier"; +import { Physics, RigidBody } from "@react-three/rapier"; import { Perf } from "r3f-perf"; import { - createContext, ReactNode, + StrictMode, Suspense, + createContext, useContext, - useState, - StrictMode, - useEffect + useState } from "react"; import { NavLink, NavLinkProps, Route, Routes } from "react-router-dom"; import { AllCollidersExample } from "./examples/all-colliders/AllCollidersExample"; @@ -24,21 +23,23 @@ import { ComponentsExample } from "./examples/components/ComponentsExample"; import { ContactForceEventsExample } from "./examples/contact-force-events/ContactForceEventsExample"; import { CradleExample } from "./examples/cradle/CradleExample"; import { Damping } from "./examples/damping/DampingExample"; +import { DynamicTypeChangeExample } from "./examples/dynamic-type-change/DynamicTypeChangeExample"; +import { ImmutablePropsExample } from "./examples/immutable-props/ImmutablePropsExample"; import { InstancedMeshes } from "./examples/instanced-meshes/InstancedMeshesExample"; import { InstancedMeshesCompound } from "./examples/instances-meshes-compound/InstancedMeshesCompoundExample"; import { Joints } from "./examples/joints/JointsExample"; import { Kinematics } from "./examples/kinematics/KinematicsExample"; +import { LockedTransformsExample } from "./examples/locked-transforms/LockedTransformsExample"; import { ManualStepExample } from "./examples/manual-step/ManualStepExamples"; import { MeshColliderTest } from "./examples/mesh-collider-test/MeshColliderExample"; -import { SensorsExample } from "./examples/sensors/SensorsExample"; -import Shapes from "./examples/plinko/ShapesExample"; -import { Transforms } from "./examples/transforms/TransformsExample"; -import { LockedTransformsExample } from "./examples/locked-transforms/LockedTransformsExample"; import { PerformanceExample } from "./examples/performance/PeformanceExample"; -import { DynamicTypeChangeExample } from "./examples/dynamic-type-change/DynamicTypeChangeExample"; +import Shapes from "./examples/plinko/ShapesExample"; +import { RopeJointExample } from "./examples/rope-joint/RopeJointExample"; +import { SensorsExample } from "./examples/sensors/SensorsExample"; +import { SnapshotExample } from "./examples/snapshot/SnapshotExample"; +import { SpringExample } from "./examples/spring/SpringExample"; import { StutteringExample } from "./examples/stuttering/StutteringExample"; -import { ImmutablePropsExample } from "./examples/immutable-props/ImmutablePropsExample"; -import { SnapshotExample } from './examples/snapshot/SnapshotExample'; +import { Transforms } from "./examples/transforms/TransformsExample"; const demoContext = createContext<{ setDebug?(f: boolean): void; @@ -116,7 +117,9 @@ const routes: Record = { "dynamic-type-changes": , stuttering: , "immutable-props": , - snapshot: + snapshot: , + spring: , + "rope-joint": }; export const App = () => { @@ -149,7 +152,6 @@ export const App = () => { interpolate={interpolate} debug={debug} timeStep={1 / 60} - // erp={0.2} > { @@ -20,12 +19,6 @@ const Rod = (props: RigidBodyOptions) => { [0, 0, 0] ]); - const { world } = useRapier(); - - useEffect(() => { - world.maxStabilizationIterations = 10; - }, []); - return ( diff --git a/demo/src/examples/rope-joint/RopeJointExample.tsx b/demo/src/examples/rope-joint/RopeJointExample.tsx new file mode 100644 index 00000000..968a3b05 --- /dev/null +++ b/demo/src/examples/rope-joint/RopeJointExample.tsx @@ -0,0 +1,120 @@ +import { Sphere, Box } from "@react-three/drei"; +import { + BallCollider, + RapierRigidBody, + RigidBody, + RigidBodyOptions, + useRopeJoint +} from "@react-three/rapier"; +import { useRef } from "react"; +import { Demo } from "../../App"; +import { Vector3 } from "@react-three/fiber"; + +const WALL_COLORS = ["#50514F", "#CBD4C2", "#FFFCFF", "#247BA0", "#C3B299"]; + +interface BoxRigidBodyProps extends RigidBodyOptions { + color: string; +} + +interface BoxWallProps extends RigidBodyOptions { + height: number; + width: number; +} + +interface RopeJointProps { + anchorPosition: Vector3; + ballPosition: Vector3; + ropeLength: number; +} + +const Floor = (props: RigidBodyOptions) => { + return ( + + + + + + ); +}; + +const BoxRigidBody = (props: BoxRigidBodyProps) => { + return ( + + + + + + ); +}; + +const BoxWall = ({ height, width, ...props }: BoxWallProps) => { + const wall = []; + + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + const position: [number, number, number] = [j, i, 0]; + wall.push( + + ); + } + } + + return ( + + {wall.map((box, i) => box)} + + ); +}; + +const RopeJoint = ({ + anchorPosition, + ballPosition, + ropeLength +}: RopeJointProps) => { + const anchor = useRef(null); + const ball = useRef(null); + + useRopeJoint(anchor, ball, [[0, 0, 0], [0, 0, 0], ropeLength]); + + return ( + + {/* Anchor */} + + + {/* Wrecking Ball */} + + + + + + + + + ); +}; + +export const RopeJointExample: Demo = () => { + return ( + + + + + + ); +}; diff --git a/demo/src/examples/spring/SpringExample.tsx b/demo/src/examples/spring/SpringExample.tsx new file mode 100644 index 00000000..29dd1a80 --- /dev/null +++ b/demo/src/examples/spring/SpringExample.tsx @@ -0,0 +1,113 @@ +import { Box, Sphere } from "@react-three/drei"; +import { + BallCollider, + RapierRigidBody, + RigidBody, + RigidBodyOptions, + useSpringJoint +} from "@react-three/rapier"; +import { forwardRef, useMemo, useRef } from "react"; +import { Demo } from "../../App"; +import { useForwardedRef } from "@react-three/rapier/src/hooks/use-forwarded-ref"; +import { vectorArrayToVector3 } from "@react-three/rapier/src/utils/utils"; + +const COLORS_ARR = ["#335C67", "#FFF3B0", "#E09F3E", "#9E2A2B", "#540B0E"]; + +interface BallSpringProps extends RigidBodyOptions { + jointNum: number; + total: number; +} + +interface BoxRigidBodyProps extends RigidBodyOptions { + color: string; +} + +const BoxRigidBody = ({ color, ...props }: BoxRigidBodyProps) => { + return ( + + + + + + ); +}; + +const BallSpring = forwardRef( + (props, floorRef) => { + const floor = useForwardedRef(floorRef); + const ball = useRef(null); + + const stiffness = 1.0e3; + const criticalDamping = 2.0 * Math.sqrt(stiffness * (props.mass ?? 1)); + const dampingRatio = props.jointNum / (props.total / 2); + const damping = dampingRatio * criticalDamping; + + const ballPos = props.position as THREE.Vector3; + + if (!ballPos) { + throw new Error("BallSpring requires a position prop"); + } + + useSpringJoint(ball, floor, [ + [0, 0, 0], + [ballPos.x, ballPos.y - 3, ballPos.z], + 0, + stiffness, + damping + ]); + + return ( + + + + + + + ); + } +); + +export const SpringExample: Demo = () => { + const floor = useRef(null); + + const vectorArr = useMemo(() => { + return Array.from({ length: 30 }).map((_, i) => { + return vectorArrayToVector3([-20 + 1.5 * (i + 1), 7.5, -30]); + }); + }, []); + + return ( + <> + + + {vectorArr.map((_, i) => ( + + + + + ))} + + ); +}; diff --git a/packages/react-three-rapier/package.json b/packages/react-three-rapier/package.json index e03268f9..1de2da64 100644 --- a/packages/react-three-rapier/package.json +++ b/packages/react-three-rapier/package.json @@ -29,7 +29,7 @@ "three": ">=0.139.0" }, "dependencies": { - "@dimforge/rapier3d-compat": "0.11.2", + "@dimforge/rapier3d-compat": "0.12.0", "three-stdlib": "2.23.9", "use-asset": "1.0.4" }, diff --git a/packages/react-three-rapier/readme.md b/packages/react-three-rapier/readme.md index 05e7ec96..87c15276 100644 --- a/packages/react-three-rapier/readme.md +++ b/packages/react-three-rapier/readme.md @@ -78,6 +78,8 @@ For full API outline and documentation, see 🧩 [API Docs](https://pmndrs.githu - [Spherical Joint](#spherical-joint) - [Revolute Joint](#revolute-joint) - [Prismatic Joint](#prismatic-joint) + - [Rope Joint](#rope-joint) + - [Spring Joint](#spring-joint) - [🖼 Joints Example](#-joints-example) - [Advanced hooks usage](#advanced-hooks-usage) - [Manual stepping](#manual-stepping) @@ -623,12 +625,14 @@ Joints can be made between two `RigidBodies` to provide a way to restrict a moti Joints are available in `r3/rapier` as hooks. -There are 4 different joint types available: +There are 6 different joint types available: - Fixed (two bodies are fixed together) - Spherical (two bodies are connected by a ball and socket, for things like arms or chains) - Revolute (two bodies are connected by a hinge, for things like doors or wheels) - Prismatic (two bodies are connected by a sliding joint, for things like pistons or sliders) +- Rope (limits the max distance between two bodies) +- Spring (applies a force proportional to the distance between two bodies) Each joint hook returns a RefObject containing the raw reference to the joint instance. @@ -658,6 +662,9 @@ A fixed joint ensures that two rigid-bodies don't move relative to each other. F ```tsx const JointedThing = () => { + const bodyA = useRef(null); + const bodyB = useRef(null); + const joint = useFixedJoint(bodyA, bodyB, [ // Position of the joint in bodyA's local space [0, 0, 0], @@ -690,6 +697,9 @@ The spherical joint ensures that two points on the local-spaces of two rigid-bod ```tsx const JointedThing = () => { + const bodyA = useRef(null); + const bodyB = useRef(null); + const joint = useSphericalJoint(bodyA, bodyB, [ // Position of the joint in bodyA's local space [0, 0, 0], @@ -718,6 +728,9 @@ The revolute joint prevents any relative movement between two rigid-bodies, exce ```tsx const JointedThing = () => { + const bodyA = useRef(null); + const bodyB = useRef(null); + const joint = useRevoluteJoint(bodyA, bodyB, [ // Position of the joint in bodyA's local space [0, 0, 0], @@ -749,6 +762,9 @@ The prismatic joint prevents any relative movement between two rigid-bodies, exc ```tsx const JointedThing = () => { + const bodyA = useRef(null); + const bodyB = useRef(null); + const joint = usePrismaticJoint(bodyA, bodyB, [ // Position of the joint in bodyA's local space [0, 0, 0], @@ -772,6 +788,83 @@ const JointedThing = () => { }; ``` +### Rope Joint + +The rope joint limits the max distance between two bodies. + +🧩 See [RopeJoint docs](https://pmndrs.github.io/react-three-rapier/functions/useRopeJoint.html) for available options. + +```tsx +const JointedThing = () => { + const bodyA = useRef(null); + const bodyB = useRef(null); + + const joint = useRopeJoint(bodyA, bodyB, [ + // Position of the joint in bodyA's local space + [0, 0, 0], + // Position of the joint in bodyB's local space + [0, 0, 0], + // The max distance between the two bodies / length of the rope + 1 + ]); + + return ( + + + + + + + + + ); +}; +``` + +### Spring Joint + +The spring joint applies a force proportional to the distance between two bodies. + +🧩 See [SpringJoint docs](https://pmndrs.github.io/react-three-rapier/functions/useSpringJoint.html) for available options. + +```tsx +const JointedThing = () => { + const bodyA = useRef(null); + const bodyB = useRef(null); + + const mass = 1; + const springRestLength = 0; + const stiffness = 1.0e3; + const criticalDamping = 2.0 * Math.sqrt(stiffness * mass); + const dampingRatio = props.jointNum / (props.total / 2); + const damping = dampingRatio * criticalDamping; + + const joint = useSpringJoint(bodyA, bodyB, [ + // Position of the joint in bodyA's local space + [0, 0, 0], + // Position of the joint in bodyB's local space + [0, 0, 0], + // Spring rest length + springRestLength, + // Spring stiffness + stiffness, + // Spring damping + damping + ]); + + return ( + + + + + + + + + ); +}; +``` + ### 🖼 Joints Example diff --git a/packages/react-three-rapier/src/components/Physics.tsx b/packages/react-three-rapier/src/components/Physics.tsx index 3efae2f1..e9dae607 100644 --- a/packages/react-three-rapier/src/components/Physics.tsx +++ b/packages/react-three-rapier/src/components/Physics.tsx @@ -29,7 +29,6 @@ import { RigidBodyAutoCollider, Vector3Tuple } from "../types"; - import { _matrix4, _position, @@ -39,7 +38,8 @@ import { import { rapierQuaternionToQuaternion, useConst, - vectorArrayToVector3 + vectorArrayToVector3, + vector3ToRapierVector } from "../utils/utils"; import FrameStepper from "./FrameStepper"; import { Debug } from "./Debug"; @@ -257,32 +257,37 @@ export interface PhysicsProps { gravity?: Vector3Tuple; /** - * The maximum velocity iterations the velocity-based constraint solver can make to attempt - * to remove the energy introduced by constraint stabilization. - * - * @defaultValue 1 + * Amount of penetration the engine wont attempt to correct + * @defaultValue 0.001 */ - maxStabilizationIterations?: number; + allowedLinearError?: number; /** - * The maximum velocity iterations the velocity-based friction constraint solver can make. - * - * The greater this value is, the most realistic friction will be. + * The number of solver iterations run by the constraints solver for calculating forces. + * The greater this value is, the most rigid and realistic the physics simulation will be. * However a greater number of iterations is more computationally intensive. * - * @defaultValue 8 + * @defaultValue 4 */ - maxVelocityFrictionIterations?: number; + numSolverIterations?: number; /** - * The maximum velocity iterations the velocity-based force constraint solver can make. - * - * The greater this value is, the most rigid and realistic the physics simulation will be. + * Number of addition friction resolution iteration run during the last solver sub-step. + * The greater this value is, the most realistic friction will be. * However a greater number of iterations is more computationally intensive. * * @defaultValue 4 */ - maxVelocityIterations?: number; + numAdditionalFrictionIterations?: number; + + /** + * Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration. + * Increasing this parameter will improve stability of the simulation. It will have a lesser effect than + * increasing `numSolverIterations` but is also less computationally expensive. + * + * @defaultValue 1 + */ + numInternalPgsIterations?: number; /** * The maximal distance separating two objects that will generate predictive contacts @@ -293,7 +298,22 @@ export interface PhysicsProps { predictionDistance?: number; /** - * The Error Reduction Parameter in between 0 and 1, is the proportion of the positional error to be corrected at each time step + * Minimum number of dynamic bodies in each active island + * + * @defaultValue 128 + */ + minIslandSize?: number; + + /** + * Maximum number of substeps performed by the solver + * + * @defaultValue 1 + */ + maxCcdSubsteps?: number; + + /** + * The Error Reduction Parameter in between 0 and 1, is the proportion of the positional error to be corrected at each time step. + * * @defaultValue 0.8 */ erp?: number; @@ -380,10 +400,13 @@ export const Physics: FC = (props) => { debug = false, gravity = [0, -9.81, 0], - maxStabilizationIterations = 1, - maxVelocityFrictionIterations = 8, - maxVelocityIterations = 4, + allowedLinearError = 0.001, predictionDistance = 0.002, + numSolverIterations = 4, + numAdditionalFrictionIterations = 4, + numInternalPgsIterations = 1, + minIslandSize = 128, + maxCcdSubsteps = 1, erp = 0.8 } = props; const rapier = useAsset(importRapier); @@ -420,21 +443,28 @@ export const Physics: FC = (props) => { // Update mutable props useEffect(() => { - worldProxy.gravity = vectorArrayToVector3(gravity); - worldProxy.integrationParameters.maxStabilizationIterations = - maxStabilizationIterations; - worldProxy.integrationParameters.maxVelocityFrictionIterations = - maxVelocityFrictionIterations; - worldProxy.integrationParameters.maxVelocityIterations = - maxVelocityIterations; + worldProxy.gravity = vector3ToRapierVector(gravity); + + worldProxy.integrationParameters.numSolverIterations = numSolverIterations; + worldProxy.integrationParameters.numAdditionalFrictionIterations = + numAdditionalFrictionIterations; + worldProxy.integrationParameters.numInternalPgsIterations = + numInternalPgsIterations; + + worldProxy.integrationParameters.allowedLinearError = allowedLinearError; + worldProxy.integrationParameters.minIslandSize = minIslandSize; + worldProxy.integrationParameters.maxCcdSubsteps = maxCcdSubsteps; worldProxy.integrationParameters.predictionDistance = predictionDistance; worldProxy.integrationParameters.erp = erp; }, [ worldProxy, ...gravity, - maxStabilizationIterations, - maxVelocityIterations, - maxVelocityFrictionIterations, + numSolverIterations, + numAdditionalFrictionIterations, + numInternalPgsIterations, + allowedLinearError, + minIslandSize, + maxCcdSubsteps, predictionDistance, erp ]); diff --git a/packages/react-three-rapier/src/hooks/joints.ts b/packages/react-three-rapier/src/hooks/joints.ts index 889efc10..657502bb 100644 --- a/packages/react-three-rapier/src/hooks/joints.ts +++ b/packages/react-three-rapier/src/hooks/joints.ts @@ -1,28 +1,28 @@ import { - ImpulseJoint, FixedImpulseJoint, - SphericalImpulseJoint, + ImpulseJoint, + PrismaticImpulseJoint, RevoluteImpulseJoint, - PrismaticImpulseJoint + RopeImpulseJoint, + SphericalImpulseJoint, + SpringImpulseJoint, } from "@dimforge/rapier3d-compat"; -import React, { - useRef, - useEffect, - useMemo, - useState, - MutableRefObject, - RefObject -} from "react"; +import { RefObject, useRef } from "react"; import { - useRapier, - RapierRigidBody, - UseImpulseJoint, FixedJointParams, - SphericalJointParams, + PrismaticJointParams, + RapierRigidBody, RevoluteJointParams, - PrismaticJointParams + RopeJointParams, + SphericalJointParams, + SpringJointParams, + UseImpulseJoint, + useRapier, } from ".."; -import { vectorArrayToVector3, tupleToObject } from "../utils/utils"; +import { + vector3ToRapierVector, + quaternionToRapierQuaternion +} from "../utils/utils"; import type Rapier from "@dimforge/rapier3d-compat"; import { useImperativeInstance } from "./use-imperative-instance"; @@ -88,10 +88,10 @@ export const useFixedJoint: UseImpulseJoint< body1, body2, rapier.JointData.fixed( - vectorArrayToVector3(body1Anchor), - tupleToObject(body1LocalFrame, ["x", "y", "z", "w"] as const), - vectorArrayToVector3(body2Anchor), - tupleToObject(body2LocalFrame, ["x", "y", "z", "w"] as const) + vector3ToRapierVector(body1Anchor), + quaternionToRapierQuaternion(body1LocalFrame), + vector3ToRapierVector(body2Anchor), + quaternionToRapierQuaternion(body2LocalFrame) ) ); }; @@ -114,8 +114,8 @@ export const useSphericalJoint: UseImpulseJoint< body1, body2, rapier.JointData.spherical( - vectorArrayToVector3(body1Anchor), - vectorArrayToVector3(body2Anchor) + vector3ToRapierVector(body1Anchor), + vector3ToRapierVector(body2Anchor) ) ); }; @@ -134,9 +134,9 @@ export const useRevoluteJoint: UseImpulseJoint< const { rapier } = useRapier(); const params = rapier.JointData.revolute( - vectorArrayToVector3(body1Anchor), - vectorArrayToVector3(body2Anchor), - vectorArrayToVector3(axis) + vector3ToRapierVector(body1Anchor), + vector3ToRapierVector(body2Anchor), + vector3ToRapierVector(axis) ); if (limits) { @@ -161,9 +161,9 @@ export const usePrismaticJoint: UseImpulseJoint< const { rapier } = useRapier(); const params = rapier.JointData.prismatic( - vectorArrayToVector3(body1Anchor), - vectorArrayToVector3(body2Anchor), - vectorArrayToVector3(axis) + vector3ToRapierVector(body1Anchor), + vector3ToRapierVector(body2Anchor), + vector3ToRapierVector(axis) ); if (limits) { @@ -173,3 +173,49 @@ export const usePrismaticJoint: UseImpulseJoint< return useImpulseJoint(body1, body2, params); }; + +/** + * The rope joint limits the max distance between two bodies. + * @category Hooks - Joints + */ +export const useRopeJoint: UseImpulseJoint< + RopeJointParams, + RopeImpulseJoint +> = (body1, body2, [body1Anchor, body2Anchor, length]) => { + const { rapier } = useRapier(); + + const vBody1Anchor = vector3ToRapierVector(body1Anchor); + const vBody2Anchor = vector3ToRapierVector(body2Anchor); + + const params = rapier.JointData.rope(length, vBody1Anchor, vBody2Anchor); + + return useImpulseJoint(body1, body2, params); +}; + +/** + * The spring joint applies a force proportional to the distance between two objects. + * @category Hooks - Joints + */ +export const useSpringJoint: UseImpulseJoint< + SpringJointParams, + SpringImpulseJoint +> = ( + body1, + body2, + [body1Anchor, body2Anchor, restLength, stiffness, damping] +) => { + const { rapier } = useRapier(); + + const vBody1Anchor = vector3ToRapierVector(body1Anchor); + const vBody2Anchor = vector3ToRapierVector(body2Anchor); + + const params = rapier.JointData.spring( + restLength, + stiffness, + damping, + vBody1Anchor, + vBody2Anchor + ); + + return useImpulseJoint(body1, body2, params); +}; diff --git a/packages/react-three-rapier/src/types.ts b/packages/react-three-rapier/src/types.ts index 2e03bdc4..7581dce3 100644 --- a/packages/react-three-rapier/src/types.ts +++ b/packages/react-three-rapier/src/types.ts @@ -9,7 +9,7 @@ import { TempContactManifold } from "@dimforge/rapier3d-compat"; import { Rotation, Vector } from "@dimforge/rapier3d-compat/math"; -import { Object3DProps } from "@react-three/fiber"; +import { Object3DProps, Vector3, Quaternion } from "@react-three/fiber"; import { Object3D } from "three"; import { ColliderProps } from "."; import { RigidBodyState } from "./components/Physics"; @@ -395,6 +395,17 @@ export interface RigidBodyOptions extends ColliderProps { */ restitution?: number; + /** + * Sets the number of additional solver iterations that will be run for this + * rigid-body and everything that interacts with it directly or indirectly + * through contacts or joints. + * + * Compared to increasing the global `World.numSolverIteration`, setting this + * value lets you increase accuracy on only a subset of the scene, resulting in reduced + * performance loss. + */ + additionalSolverIterations?: number; + /** * The default collision groups bitmask for all colliders in this rigid body. * Can be customized per-collider. @@ -450,31 +461,45 @@ export interface RigidBodyOptions extends ColliderProps { // Joints export type SphericalJointParams = [ - body1Anchor: Vector3Tuple, - body2Anchor: Vector3Tuple + body1Anchor: Vector3, + body2Anchor: Vector3 ]; export type FixedJointParams = [ - body1Anchor: Vector3Tuple, - body1LocalFrame: Vector4Tuple, - body2Anchor: Vector3Tuple, - body2LocalFrame: Vector4Tuple + body1Anchor: Vector3, + body1LocalFrame: Quaternion, + body2Anchor: Vector3, + body2LocalFrame: Quaternion ]; export type PrismaticJointParams = [ - body1Anchor: Vector3Tuple, - body2Anchor: Vector3Tuple, - axis: Vector3Tuple, + body1Anchor: Vector3, + body2Anchor: Vector3, + axis: Vector3, limits?: [min: number, max: number] ]; export type RevoluteJointParams = [ - body1Anchor: Vector3Tuple, - body2Anchor: Vector3Tuple, - axis: Vector3Tuple, + body1Anchor: Vector3, + body2Anchor: Vector3, + axis: Vector3, limits?: [min: number, max: number] ]; +export type RopeJointParams = [ + body1Anchor: Vector3, + body2Anchor: Vector3, + length: number +]; + +export type SpringJointParams = [ + body1Anchor: Vector3, + body2Anchor: Vector3, + restLength: number, + stiffness: number, + damping: number +]; + export interface UseImpulseJoint { ( body1: RefObject, diff --git a/packages/react-three-rapier/src/utils/utils-rigidbody.ts b/packages/react-three-rapier/src/utils/utils-rigidbody.ts index 744b5a42..190f4ab8 100644 --- a/packages/react-three-rapier/src/utils/utils-rigidbody.ts +++ b/packages/react-three-rapier/src/utils/utils-rigidbody.ts @@ -1,5 +1,5 @@ import { RigidBody, RigidBodyDesc } from "@dimforge/rapier3d-compat"; -import React, { MutableRefObject, useEffect, useMemo } from "react"; +import { useEffect, useMemo } from "react"; import { Matrix4, Object3D, Vector3 } from "three"; import { Boolean3Tuple, RigidBodyProps, Vector3Tuple } from ".."; import { @@ -81,6 +81,9 @@ export const mutableRigidBodyOptions: MutableRigidBodyOptions = { gravityScale: (rb: RigidBody, value: number) => { rb.setGravityScale(value, true); }, + additionalSolverIterations(rb: RigidBody, value: number) { + rb.setAdditionalSolverIterations(value); + }, linearDamping: (rb: RigidBody, value: number) => { rb.setLinearDamping(value); }, diff --git a/packages/react-three-rapier/src/utils/utils.ts b/packages/react-three-rapier/src/utils/utils.ts index d131d0d7..3ffce46e 100644 --- a/packages/react-three-rapier/src/utils/utils.ts +++ b/packages/react-three-rapier/src/utils/utils.ts @@ -1,36 +1,18 @@ -import React, { useRef } from "react"; +import { useRef } from "react"; import { Quaternion as RapierQuaternion, - Vector3 as RapierVector3 + Vector3 as RapierVector3, } from "@dimforge/rapier3d-compat"; - -import { Euler, Quaternion, Shape, Vector3 } from "three"; +import { Euler, Quaternion, Vector3 } from "three"; import { _euler, _quaternion, _vector3 } from "./shared-objects"; import { RigidBodyTypeString, Vector3Tuple } from "../types"; +import { Vector3 as Vector3Like, Quaternion as QuaternionLike } from "@react-three/fiber"; export const vectorArrayToVector3 = (arr: Vector3Tuple) => { const [x, y, z] = arr; return new Vector3(x, y, z); }; -export const tupleToObject = < - T extends readonly any[], - K extends readonly string[] ->( - tuple: T, - keys: K -) => { - return keys.reduce( - (obj, key, i) => { - obj[key as K[number]] = tuple[i]; - return obj; - }, - {} as { - [Key in K[number]]: T[number]; - } - ); -}; - export const vector3ToQuaternion = (v: Vector3) => { return _quaternion.setFromEuler(_euler.setFromVector3(v)); }; @@ -45,6 +27,25 @@ export const rapierQuaternionToQuaternion = ({ w }: RapierQuaternion) => _quaternion.set(x, y, z, w); +export const vector3ToRapierVector = (v: Vector3Like) => { + if (Array.isArray(v)) { + return new RapierVector3(v[0], v[1], v[2]); + } else if (typeof v === "number") { + return new RapierVector3(v, v, v); + } else { + const threeVector3 = v as THREE.Vector3; + return new RapierVector3(threeVector3.x, threeVector3.y, threeVector3.z); + } +}; + +export const quaternionToRapierQuaternion = (v: QuaternionLike) => { + if (Array.isArray(v)) { + return new RapierQuaternion(v[0], v[1], v[2], v[3]); + } else { + return new RapierQuaternion(v.x, v.y, v.z, v.w); + } +}; + const rigidBodyTypeMap = { fixed: 1, dynamic: 0, diff --git a/packages/react-three-rapier/tests/__snapshots__/physics.test.tsx.snap b/packages/react-three-rapier/tests/__snapshots__/physics.test.tsx.snap index 1e9c468c..fff728d7 100644 --- a/packages/react-three-rapier/tests/__snapshots__/physics.test.tsx.snap +++ b/packages/react-three-rapier/tests/__snapshots__/physics.test.tsx.snap @@ -4,17 +4,17 @@ exports[`physics > snapshots > restores snapshots correctly 1`] = ` [ Vector3 { "x": 0, - "y": -13.761244773864746, + "y": -13.659093856811523, "z": 0, }, Vector3 { "x": 2, - "y": -11.761244773864746, + "y": -11.659093856811523, "z": 2, }, Vector3 { "x": -2, - "y": -15.761244773864746, + "y": -15.659093856811523, "z": -2, }, ] @@ -24,17 +24,17 @@ exports[`physics > snapshots > restores snapshots correctly 2`] = ` [ Vector3 { "x": 0, - "y": -13.761244773864746, + "y": -13.659093856811523, "z": 0, }, Vector3 { "x": 2, - "y": -11.761244773864746, + "y": -11.659093856811523, "z": 2, }, Vector3 { "x": -2, - "y": -15.761244773864746, + "y": -15.659093856811523, "z": -2, }, ] diff --git a/packages/react-three-rapier/tests/physics.test.tsx b/packages/react-three-rapier/tests/physics.test.tsx index c06ee473..da9d783a 100644 --- a/packages/react-three-rapier/tests/physics.test.tsx +++ b/packages/react-three-rapier/tests/physics.test.tsx @@ -67,14 +67,14 @@ describe("physics", () => { }); expect(vec3(rigidBody.current?.translation()).toArray()).to.deep.eq([ - 0.6666666865348816, 0.6639417409896851, 0.6666666865348816 + 0.6666666269302368, 0.6649635434150696, 0.6666666269302368, ]); await pause(100); // expect nothing to have changed expect(vec3(rigidBody.current?.translation()).toArray()).to.deep.eq([ - 0.6666666865348816, 0.6639417409896851, 0.6666666865348816 + 0.6666666269302368, 0.6649635434150696, 0.6666666269302368 ]); }); }); diff --git a/yarn.lock b/yarn.lock index dda8b6d7..52faa5ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1595,10 +1595,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@dimforge/rapier3d-compat@0.11.2": - version "0.11.2" - resolved "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.11.2.tgz" - integrity sha512-vdWmlkpS3G8nGAzLuK7GYTpNdrkn/0NKCe0l1Jqxc7ZZOB3N0q9uG/Ap9l9bothWuAvxscIt0U97GVLr0lXWLg== +"@dimforge/rapier3d-compat@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz#7b3365e1dfdc5cd957b45afe920b4ac06c7cd389" + integrity sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow== "@esbuild/android-arm64@0.17.14": version "0.17.14"