Giter VIP home page Giter VIP logo

react-three-rapier's Introduction

@react-three/rapier

โš ๏ธ This library is under development. All APIs are subject to change. โš ๏ธ
For contributions, please read the ๐Ÿชง Contribution Guide.
For available APIs, see ๐Ÿงฉ API Docs


react-three/rapier (or r3/rapier) is a wrapper library around the Rapier (https://rapier.rs/docs/user_guides/javascript) WASM-based physics engine, designed to slot seamlessly into a react-three/fiber pipeline.

The goal of this library to is to provide a fast physics engine with minimal friction and small, straight forward API.

Basic Usage

import { Box, Torus } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier";

const App = () => {
  return (
    <Canvas>
      <Suspense>
        <Physics debug>
          <RigidBody colliders={"hull"} restitution={2}>
            <Torus />
          </RigidBody>

          <CuboidCollider position={[0, -2, 0]} args={[20, 0.5, 20]} />
        </Physics>
      </Suspense>
    </Canvas>
  );
};

๐Ÿ“ Readme note

Below follows a guide on core concepts for react-three/rapier.
For full API outline and documentation, see ๐Ÿงฉ API Docs.


Readme Topics


The Physics Component

The <Physics /> component is the root component of your physics world. It is responsible for creating the physics world and managing the simulation. It relies on lazily initiating Rapier and needs to be wrapped in <Suspense />.

๐Ÿงฉ See PhysicsProps docs for available props.

const Scene = () => {
  return (
    <Canvas>
      <Suspense>
        <Physics gravity={[0, 1, 0]} interpolation={false} colliders={false}>
          ...
        </Physics>
      </Suspense>
    </Canvas>
  );
};

The RigidBody Component

The <RigidBody /> component is used to add a mesh into the physics world. You use it by wrapping one or more meshes and setting desired props. By default, this will automatically generate Colliders based on the shape of the wrapped meshes (see Automatic colliders).

๐Ÿงฉ See RigidBodyProps docs for available props.

const RigidBodyMesh = () => (
  <RigidBody>
    <mesh />
  </RigidBody>
);

Automatic Colliders

RigidBodies generate automatic colliders by default for all meshes that it contains. You can control the default collider by setting the colliders prop on a <RigidBody />, or change it globally by setting colliders on <Physics />. Setting colliders={false} disables auto-generation.

Supported values:

  • "cuboid", creates a CuboidCollider based on the bounding box of the mesh
  • "ball", creates a SphereCollider based on the bounding sphere of the mesh
  • "trimesh", creates a TrimeshCollider based on the mesh's geometry
  • "hull", creates a ConvexHullCollider based on the mesh's geometry
  • false, disables auto-generation

Generate ConvexHull colliders for all meshes in a RigidBody by default:

const Scene = () => (
  <Physics colliders="hull">
    <RigidBody>
      <Box />
    </RigidBody>
    <RigidBody position={[0, 10, 0]}>
      <Sphere />
    </RigidBody>
  </Physics>
);

Turn off automatic collider generation globally, but apply auto generation locally:

const Scene = () => (
  <Physics colliders={false}>
    {/* Use an automatic CuboidCollider for all meshes inside this RigidBody */}
    <RigidBody colliders="cuboid">
      <Box />
    </RigidBody>

    {/* Use an automatic BallCollider for all meshes inside this RigidBody */}
    <RigidBody position={[0, 10, 0]} colliders="ball">
      <Sphere />
    </RigidBody>
  </Physics>
);

Collider Components

You can also create Colliders by hand and add them to a RigidBody to create compound colliders. This is useful for creating more complex shapes, for creating simplified shapes for performance reasons, or for detecting collisions on specific parts of a mesh.

๐Ÿงฉ See ColliderProps docs for available props.

const Scene = () => (<>
  {/* Make a compound shape with two custom BallColliders */}
  <RigidBody position={[0, 10, 0]}>
    <Sphere />
    <BallCollider args={[0.5]} />
    <BallCollider args={[0.5]} position={[1, 0, 0]} />
  </RigidBody>

  {/* Make a compound shape with two custom BallColliders, an automatic BallCollider,
      Two automatic MeshColliders, based on two different shape types */}
  <RigidBody position={[0, 10, 0]} colliders='ball'>
    <MeshCollider type="trimesh">
      <mesh ... />
    </MeshCollider>

    <MeshCollider type="hull">
      <mesh ... />
    </MeshCollider>

    <Sphere />

    <BallCollider args={[0.5]} />
    <BallCollider args={[0.5]} position={[1, 0, 0]} />
  </RigidBody>
<>)

RigidBodies work inside other transformed objects as well. Simulation runs in world space and is transformed to the objects local space, so that things act as you'd expect.

Note It's always best to create RigidBodies where the center of gravity is in the center of the object, otherwise you might get some unexpected behavior during simulation interpolation.

import { Box } from "@react-three/drei";
import { RigidBody, CuboidCollider } from "@react-three/rapier";

const Scene = () => (
  <group position={[2, 5, 0]} rotation={[0, 0.3, 2]}>
    <RigidBody>
      <Box />
      <CuboidCollider args={[0.5, 0.5, 0.5]} />
    </RigidBody>
  </group>
);

If part of our meshes are invisible and you want to include them in the collider creation, use the includeInvisible flag.

<RigidBody colliders="hull" includeInvisible>
  <object3D>
    <Suzanne visible={false} />
  </object3D>
</RigidBody>

๐Ÿ–ผ Collider Examples

Instanced Meshes

Instanced meshes can also be used and have automatic colliders generated from their mesh.

By wrapping exactly one Three.InstancedMesh in <InstancedRigidBodies />, each instance will be attached to an individual RigidBody.

๐Ÿงฉ See InstancedRigidBodiesProps docs for available props.

import { InstancedRigidBodies, RapierRigidBody } from "@react-three/rapier";

const COUNT = 1000;

const Scene = () => {
  const rigidBodies = useRef<RapierRigidBody[]>(null);

  useEffect(() => {
    if (!rigidBodies.current) {
      return;
    }

    // You can access individual instanced by their index
    rigidBodies.current[40].applyImpulse({ x: 0, y: 10, z: 0 }, true);
    rigidBodies.current.at(100).applyImpulse({ x: 0, y: 10, z: 0 }, true);

    // Or update all instances
    rigidBodies.current.forEach((api) => {
      api.applyImpulse({ x: 0, y: 10, z: 0 }, true);
    });
  }, []);

  // We can set the initial positions, and rotations, and scales, of
  // the instances by providing an array of InstancedRigidBodyProps
  // which is the same as RigidBodyProps, but with an additional "key" prop.
  const instances = useMemo(() => {
    const instances: InstancedRigidBodyProps[] = [];

    for (let i = 0; i < COUNT; i++) {
      instances.push({
        key: "instance_" + Math.random(),
        position: [Math.random() * 10, Math.random() * 10, Math.random() * 10],
        rotation: [Math.random(), Math.random(), Math.random()]
      });
    }

    return instances;
  }, []);

  return (
    <InstancedRigidBodies
      ref={rigidBodies}
      instances={instances}
      colliders="ball"
    >
      <instancedMesh args={[undefined, undefined, COUNT]} count={COUNT} />
    </InstancedRigidBodies>
  );
};

We can also create compound shapes for instanced meshes by providing an array of Colliders in the colliderNodes prop.

import {
  InstancedRigidBodies,
  BoxCollider,
  SphereCollider
} from "@react-three/rapier";
const COUNT = 500;

const Scene = () => {
  const instances = useMemo(() => {
    const instances: InstancedRigidBodyProps[] = [];

    for (let i = 0; i < COUNT; i++) {
      instances.push({
        key: "instance_" + Math.random(),
        position: [Math.random() * 10, Math.random() * 10, Math.random() * 10],
        rotation: [Math.random(), Math.random(), Math.random()]
      });
    }

    return instances;
  }, []);

  return (
    <InstancedRigidBodies
      instances={instances}
      colliders="ball"
      colliderNodes={[
        <BoxCollider args={[0.5, 0.5, 0.5]} />,
        <SphereCollider args={[0.5]} />
      ]}
    >
      <instancedMesh args={[undefined, undefined, COUNT]} count={COUNT} />
    </InstancedRigidBodies>
  );
};

Debug

Set the debug prop on <Physics /> to see live representations of all colliders in a scene, using the live debug buffer from the physics engine.

import { Box, Sphere } from "@react-three/drei";
import { RigidBody } from "@react-three/rapier";

const Scene = () => {
  return (
    <Physics debug>
      <RigidBody>
        <Box />
      </RigidBody>
      <RigidBody>
        <Sphere />
      </RigidBody>
    </Physics>
  );
};

Moving things around, and applying forces

You can access the instance for a RigidBody by storing its ref. This allows you to perform any operation on the underlying physics object directly.

r3/rapier exposes a RapierRigidBody and RapierCollider as aliases for rapiers underlying base objects.

For all available methods, see the Rapier docs.

import { RigidBody, RapierRigidBody } from "@react-three/rapier";

const Scene = () => {
  const rigidBody = useRef<RapierRigidBody>(null);

  useEffect(() => {
    if (rigidBody.current) {
      // A one-off "push"
      rigidBody.current.applyImpulse({ x: 0, y: 10, z: 0 }, true);

      // A continuous force
      rigidBody.current.addForce({ x: 0, y: 10, z: 0 }, true);

      // A one-off torque rotation
      rigidBody.current.applyTorqueImpulse({ x: 0, y: 10, z: 0 }, true);

      // A continuous torque
      rigidBody.current.addTorque({ x: 0, y: 10, z: 0 }, true);
    }
  }, []);

  return (
    <RigidBody ref={rigidBody}>
      <mesh>
        <boxBufferGeometry />
        <meshStandardMaterial />
      </mesh>
    </RigidBody>
  );
};

Rapier's API returns quaternions and vectors that are not compatible with Three.js, r3/rapier therefore exposes some helper functions (vec3, quat, euler) for quick type conversions. These helper functions can also be used as a shorthand for creating new objects.

import { RapierRigidBody, quat, vec3, euler } from "@react-three/rapier";

const Scene = () => {
  const rigidBody = useRef<RapierRigidBody>(null);

  useEffect(() => {
    if (rigidBody.current) {
      const position = vec3(rigidBody.current.translation());
      const quaternion = quat(rigidBody.current.rotation());
      const eulerRot = euler().setFromQuaternion(
        quat(rigidBody.current.rotation())
      );

      // While Rapier's return types need conversion, setting values can be done directly with Three.js types
      rigidBody.current.setTranslation(position, true);
      rigidBody.current.setRotation(quaternion, true);
      rigidBody.current.setAngvel({ x: 0, y: 2, z: 0 }, true);
    }
  }, []);

  return (
    <RigidBody ref={rigidBody}>
      <mesh>
        <boxBufferGeometry />
        <meshStandardMaterial />
      </mesh>
    </RigidBody>
  );
};

Collision Events

You can subscribe to collision and state events on a RigidBody:

๐Ÿงฉ See onCollisionEnter / onCollisionExit docs for more information.

const RigidBottle = () => {
  const [isAsleep, setIsAsleep] = useState(false);

  return (
    <RigidBody
      colliders="hull"
      onSleep={() => setIsAsleep(true)}
      onWake={() => setIsAsleep(false)}
      name="Bally McBallFace"
      onCollisionEnter={({ manifold, target, other }) => {
        console.log(
          "Collision at world position ",
          manifold.solverContactPoint(0)
        );

        if (other.rigidBodyObject) {
          console.log(
            // this rigid body's Object3D
            target.rigidBodyObject.name,
            " collided with ",
            // the other rigid body's Object3D
            other.rigidBodyObject.name
          );
        }
      }}
    >
      <Sphere>
        <meshPhysicalMaterial color={isAsleep ? "white" : "blue"} />
      </Sphere>
    </RigidBody>
  );
};

You may also subscribe to collision events on individual Colliders:

<CuboidCollider
  onCollisionEnter={(payload) => {
    /* ... */
  }}
  onCollisionExit={(payload) => {
    /* ... */
  }}
/>

The payload object for all collision callbacks contains the following properties:

  • target
    CollisionTarget of the object firing the event.
  • other
    CollisionTarget of the other object involved in the event.
  • manifold (onCollisionEnter only)
    The contact manifold generated by the collision event.
  • flipped (onCollisionEnter only)
    true if the data in the manifold is flipped.

A CollisionTarget is an object containing references to objects involved in a collision event. It has the following properties:

  • rigidBody (if exists): Rapier.RigidBody
  • rigidBodyObject (if exists): Three.Object3D
  • collider: Rapier.Collider
  • colliderObject: Three.Object3D

Configuring collision and solver groups

Both <RigidBody> as well as all collider components allow you to configure collisionsGroups and solverGroups properties that configures which groups the colliders are in, and what other groups they should interact with in potential collision and solving events (you will find more details on this in the Rapier documentation.)

Since these are set as bitmasks and bitmasks can get a bit unwieldy to generate, this library provides a helper called interactionGroups that can be used to generate bitmasks from numbers and arrays of groups, where groups are identified using numbers from 0 to 15.

The first argument is the group, or an array of groups, that the collider is a member of; the second argument is the group, or an array of groups, that the collider should interact with.

Here the collider is in group 0, and interacts with colliders from groups 0, 1 and 2:

<CapsuleCollider collisionGroups={interactionGroups(0, [0, 1, 2])} />

This collider is in multiple groups, but only interacts with colliders from a single group:

<CapsuleCollider collisionGroups={interactionGroups([0, 5], 7)} />

When the second argument is omitted, the collider will interact with all groups:

<CapsuleCollider collisionGroups={interactionGroups(12)} />

Note Please remember that in Rapier, for a collision (or solving) event to occur, both colliders involved in the event must match the related interaction groups -- a one-way match will be ignored.

Note By default, colliders are members of all groups, and will interact with all other groups.

Contact force events

Contact force events are triggered on <RigidBody> and any collider components when two objects collider.

<RigidBody
  colliders="ball"
  onContactForce={(payload) => {
    console.log(`The total force generated was: ${payload.totalForce}`);
  }}
>
  <Sphere>
    <meshPhysicalMaterial color={"grey"} />
  </Sphere>
</RigidBody>

The payload for the contact force event contains the following properties:

  • target
    CollisionTarget of the object firing the event
  • other
    CollisionTarget of the other object involved in the event
  • totalForce
    The sum of all the forces between the two colliders
  • totalForceMagnitude
    The sum of the magnitudes of each force between the two colliders
  • maxForceDirection
    The magnitude of the largest force at a contact point of this contact pair
  • maxForceMagnitude
    The world-space (unit) direction of the force with strongest magnitude

More information about each property can be found in the rapier TempContactForceEvent API documentation.

You can also add the onContactForce event to any collider.

<CapsuleCollider
  onContactForce={(payload) => {
    /* ... */
  }}
/>

Sensors

A Collider can be set to be a sensor, which means that it will not generate any contact points, and will not be affected by forces. This is useful for detecting when a collider enters or leaves another collider, without affecting the other collider.

To detect when a collider enters or leaves another collider, you can use the onIntersectionEnter and onIntersectionExit events on the collider.

๐Ÿงฉ See onIntersectionEnter / onIntersectionExit docs for more information.

<RigidBody>
  <GoalPosts />

  <CuboidCollider
    args={[5, 5, 1]}
    sensor
    onIntersectionEnter={() => console.log("Goal!")}
  />
</RigidBody>

๐Ÿ–ผ Sensors Example

Configuring Time Step Size

By default, <Physics> will simulate the physics world at a fixed rate of 60 frames per second. This can be changed by setting the timeStep prop on <Physics>:

<Physics timeStep={1 / 30}>{/* ... */}</Physics>

The timeStep prop may also be set to "vary", which will cause the simulation's time step to adjust to every frame's frame delta:

<Physics timeStep="vary">{/* ... */}</Physics>

Note This is useful for games that run at variable frame rates, but may cause instability in the simulation. It also prevents the physics simulation from being fully deterministic. Please use with care!

Joints

Joints can be made between two RigidBodies to provide a way to restrict a motion of a body in relation to another.

Read more about joints in Rapier: https://rapier.rs/docs/user_guides/javascript/joints

Joints are available in r3/rapier as hooks.

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.

const WheelJoint = ({ bodyA, bodyB }) => {
  const joint = useRevoluteJoint(bodyA, bodyB, [
    [0, 0, 0],
    [0, 0, 0],
    [0, 0, 0]
  ]);

  useFrame(() => {
    if (joint.current) {
      joint.current.configureMotorVelocity(10, 2);
    }
  }, []);

  return null;
};

Fixed Joint

A fixed joint ensures that two rigid-bodies don't move relative to each other. Fixed joints are characterized by one local frame (represented by an isometry) on each rigid-body. The fixed-joint makes these frames coincide in world-space.

๐Ÿงฉ See FixedJoint docs for available options.

const JointedThing = () => {
  const bodyA = useRef<RapierRigidBody>(null);
  const bodyB = useRef<RapierRigidBody>(null);

  const joint = useFixedJoint(bodyA, bodyB, [
    // Position of the joint in bodyA's local space
    [0, 0, 0],
    // Orientation of the joint in bodyA's local space
    [0, 0, 0, 1],
    // Position of the joint in bodyB's local space
    [0, 0, 0],
    // Orientation of the joint in bodyB's local space
    [0, 0, 0, 1]
  ]);

  return (
    <group>
      <RigidBody ref={bodyA}>
        <mesh />
      </RigidBody>
      <RigidBody ref={bodyB}>
        <mesh />
      </RigidBody>
    </group>
  );
};

Spherical Joint

The spherical joint ensures that two points on the local-spaces of two rigid-bodies always coincide (it prevents any relative translational motion at this points).

๐Ÿงฉ See SphericalJoint docs for available options.

const JointedThing = () => {
  const bodyA = useRef<RapierRigidBody>(null);
  const bodyB = useRef<RapierRigidBody>(null);

  const joint = useSphericalJoint(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]
  ]);

  return (
    <group>
      <RigidBody ref={bodyA}>
        <mesh />
      </RigidBody>
      <RigidBody ref={bodyB}>
        <mesh />
      </RigidBody>
    </group>
  );
};

Revolute Joint

The revolute joint prevents any relative movement between two rigid-bodies, except for relative rotations along one axis. This is typically used to simulate wheels, fans, etc.

๐Ÿงฉ See RevoluteJoint docs for available options.

const JointedThing = () => {
  const bodyA = useRef<RapierRigidBody>(null);
  const bodyB = useRef<RapierRigidBody>(null);
  
  const joint = useRevoluteJoint(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],
    // Axis of the joint, expressed in the local-space of
    // the rigid-bodies it is attached to. Cannot be [0,0,0].
    [0, 1, 0]
  ]);

  return (
    <group>
      <RigidBody ref={bodyA}>
        <mesh />
      </RigidBody>
      <RigidBody ref={bodyB}>
        <mesh />
      </RigidBody>
    </group>
  );
};

Prismatic Joint

The prismatic joint prevents any relative movement between two rigid-bodies, except for relative translations along one axis.

๐Ÿงฉ See PrismaticJoint docs for available options.

const JointedThing = () => {
  const bodyA = useRef<RapierRigidBody>(null);
  const bodyB = useRef<RapierRigidBody>(null);

  const joint = usePrismaticJoint(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],
    // Axis of the joint, expressed in the local-space of
    // the rigid-bodies it is attached to. Cannot be [0,0,0].
    [0, 1, 0]
  ]);

  return (
    <group>
      <RigidBody ref={bodyA}>
        <mesh />
      </RigidBody>
      <RigidBody ref={bodyB}>
        <mesh />
      </RigidBody>
    </group>
  );
};

Rope Joint

The rope joint limits the max distance between two bodies.

๐Ÿงฉ See RopeJoint docs for available options.

const JointedThing = () => {
  const bodyA = useRef<RapierRigidBody>(null);
  const bodyB = useRef<RapierRigidBody>(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 (
    <group>
      <RigidBody ref={bodyA}>
        <mesh />
      </RigidBody>
      <RigidBody ref={bodyB}>
        <mesh />
      </RigidBody>
    </group>
  );
};

Spring Joint

The spring joint applies a force proportional to the distance between two bodies.

๐Ÿงฉ See SpringJoint docs for available options.

const JointedThing = () => {
  const bodyA = useRef<RapierRigidBody>(null);
  const bodyB = useRef<RapierRigidBody>(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 (
    <group>
      <RigidBody ref={bodyA}>
        <mesh />
      </RigidBody>
      <RigidBody ref={bodyB}>
        <mesh />
      </RigidBody>
    </group>
  );
};

๐Ÿ–ผ Joints Example

Advanced hooks usage

Advanced users might need granular access to the physics loop and direct access to the world instance. This can be done by using the following hooks:

  • useRapier
    Gives you access to the world, direct access to rapier, and more.
    ๐Ÿงฉ See useRapier docs for more information.
  • useBeforePhysicsStep
    Allows you to run code before the physics simulation is stepped.
    ๐Ÿงฉ See useBeforePhysicsStep docs for more information.
  • useAfterPhysicsStep Allows you to run code after the physics simulation is stepped.
    ๐Ÿงฉ See useAfterPhysicsStep docs for more information.

Manual stepping

You can manually step the physics simulation by calling the step method from the useRapier hook.

const { step } = useRapier();

step(1 / 60);

On-demand rendering

By default @react-three/rapier will update the physics simulation when a frame renders. This is fine for most cases, but if you want to only render the scene when things have changed, you need to run the physics simulation independently from the render loop.

Setting <Physics updateLoop="independent" /> will make the physics simulation run in it's own requestAnimationFrame loop, and call invalidate on the canvas only when there are active (moving) bodies.

<Canvas frameloop="demand">
  <Physics updateLoop="independent">...</Physics>
</Canvas>

Snapshots

The world can be serialized as a Uint8Array using world.takeSnapshot(), see Rapier's docs on Serialization for more info.

The snapshot can be used to construct a new world. In r3/rapier, you need to replace the world with this snapshot.

Note

This only works if the snapshotted world is identical to the restored one. If objects, or the order of creation of objects vary, expect RigidBodies to scramble.

import { useRapier } from '@react-three/rapier';

const SnapshottingComponent = () => {
  const { world, setWorld, rapier } = useRapier();
  const worldSnapshot = useRef<Uint8Array>();

  // Store the snapshot
  const takeSnapshot = () => {
    const snapshot = world.takeSnapshot()
    worldSnapshot.current = snapshot
  }

  // Create a new World from the snapshot, and replace the current one
  const restoreSnapshot = () => {
    setWorld(rapier.World.restoreSnapshot(worldSnapshot.current))
  }

  return <>
    <Rigidbody>...</RigidBody>
    <Rigidbody>...</RigidBody>
    <Rigidbody>...</RigidBody>
    <Rigidbody>...</RigidBody>
    <Rigidbody>...</RigidBody>
  </>
}

react-three-rapier's People

Contributors

0xtito avatar alexandernanberg avatar balraj-johal avatar bvkimball avatar codyjasonbennett avatar console-buche avatar davcri avatar dependabot[bot] avatar drcmda avatar edwinwebb avatar firtoz avatar github-actions[bot] avatar glavin001 avatar hmans avatar isaac-mason avatar lakshjaisinghani avatar machado2 avatar micmania1 avatar planktonrobo avatar rodrigohamuy avatar roombawulf avatar vegancat avatar vynetic avatar wiledal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-three-rapier's Issues

Is Rigidbody supposed to be compatible with the TransformControls of Threejs

Maybe related to this issue #53, but I wanted to know if the Rigidbody component is supposed to be compatible with the ThreeJS TransformControls.

I'm building a game engine and react-three-rapier seems to be the best physic engine for it, but the thing is when I want to drag an item that has physic with my TransformControls implementation it seems buggy.

I tried on a vanilla react-three-fiber and it seems to be buggy as well.

Here is my dirt codesandbox that represents the issue.
https://codesandbox.io/p/github/fgarrec0397/rapier-issue/draft/nostalgic-sammet?file=%2Fsrc%2FApp.tsx

Just drag the cube and you will see what I'm talking about.

Thanks a lot!

Types: RigidBody and Collider should accept Vector3 positions

<RigidBody> and the collider components accept position props that are currently typed to [number, number, number]. They also work fine with Vector3 instances (which is great!), but this is currently not reflected in the types:

image

(new Vector3() instances in the screenshot are purely for demonstration purposes. ๐Ÿคก )

Add support for declaratively setting RigidBody userData

I ran into a case where I wanted to add user data to a rigid body in order to read it when raycasting. I can do this now with a ref and the raw api, but it would be nice to be able to set rapier user data on <RigidBody /> declaratively.

I have noted that the RigidBody component and Collider components already have a userData prop for body/collider Object3Ds.


Here are some approaches I've thought of:

Option 1: add a rapierUserData prop for the RigidBody userData

Fairly self-explanatory. One con is if you want some user data on both the Object3D + the rigid body, two props are needed.

Option 2: use the userData prop as both the three Object3D userData and the rapier RigidBody userData

Both three.js Object3D userData and rapier RigidBody userData are just arbitrary objects. We could set both of these to the one userData prop. There could be some potential footguns though around expectations of Object3D userData and RigidBody userData always being equal.


Option 1 would be my preference for its simplicity, but keen to get thoughts. I'm happy to do the implementation too ๐Ÿ™‚

How to synchronize the positions of physical objects with the store?

I use zustand as a store provider.
I process every position change through callbacks

            const threeObject = scene.getObjectByName(selectedObject.id);
            if (threeObject)
              moveObject(
                selectedObject,
                new V3(
                  threeObject.position.x,
                  threeObject.position.y,
                  threeObject.position.z
                )
              );
  • TransformPanel - onChange
    image
       moveObject(
              selectedObject,
              new V3(v, selectedObject.position.y, selectedObject.position.z)
            );

How can I handle position changes of physical bodies?

P.S.

  moveObject: (gameObject, position) => changeObject(gameObject, { position })
const changeObject = (gameObject: GameObject, props: Partial<GameObject>) => {
useSceneStore.setState((old) => {
  const copy = [...old.objects];
  const index = copy.findIndex(({ id }) => id === gameObject.id);
  copy[index] = { ...gameObject, ...props };
  return { objects: copy };
});
if (useSceneStore.getState().selectedObject?.id === gameObject.id)
  useSceneStore.getState().selectObject({ ...gameObject, ...props });
};

Side effects in useMemo

First I just want to say great work with this!

I was reading through the code and noticed that you're calling world.createRigidBody inside useMemo which breaks some React rules since world.createRigidBody has side effects. useMemo isn't the best choice for this anyway because React can decide to throw away the value at any time and recompute it.

const rigidBody = useMemo(() => {
const [lvx, lvy, lvz] = options?.linearVelocity ?? [0, 0, 0];
const [avx, avy, avz] = options?.angularVelocity ?? [0, 0, 0];
const gravityScale = options?.gravityScale ?? 1;
const canSleep = options?.canSleep ?? true;
const ccdEnabled = options?.ccd ?? false;
const type = rigidBodyTypeFromString(options?.type || "dynamic");
const [x, y, z] = options?.position || [0, 0, 0];
const [rx, ry, rz] = options?.rotation || [0, 0, 0];
const rigidBodyDesc = new RAPIER.RigidBodyDesc(type)
.setLinvel(lvx, lvy, lvz)
.setAngvel({ x: avx, y: avy, z: avz })
.setGravityScale(gravityScale)
.setCanSleep(canSleep)
.setCcdEnabled(ccdEnabled)
.setTranslation(0,0,0)
const body = world.createRigidBody(rigidBodyDesc);
return body;
}, []);

In my own integration I ended up using a getter which as far as I can tell should be React 18+ safe. The approach is described in the bottom of reactwg/react-18#18

https://github.com/alexandernanberg/playground/blob/973c512aa192a6500a503a15aea6d6dd1b83d2be/src/components/physics.tsx#L265-L272

World simulation unexpectedly speeds up

When I leave a project that uses react-three/rapier running in a background tab for a couple of minutes and then return to it, the speed at which the physics simulation runs is significantly increased for a little while, as if the fixed step look is trying to catch up on missed frames.

issue with generating colliders

Hello
I'm using CuboidCollider for generating a limitless flat plane
problem is visible in image below

image

first three block on left have collider and other three on right have no any collider
code for each of them is same and don't now why it's ignoring that part
and when I refresh the page
it keeps ignoring that three!
I'll attach the Code here too
<RigidBody colliders={false} type="fixed"> <Center position-y={-2}> <mesh material={props.material} receiveShadow scale={[props.scale, 4, props.scale]} > <boxGeometry /> </mesh> </Center> <CuboidCollider args={[1, 1, 1]} scale={[props.scale / 2, 2, props.scale / 2]} position={[0, -2, 0]} /> </RigidBody>

is there any way to solve this issue?
Thank you

Wasm build

Following the discussion on #113 :

It would be nice to have the option to use the rapier3d Wasm build instead of the rapier3d-compat build.

Size on disk as of 0.9.0:
rapier3d (Wasm): 1.4MB
rapier3d-compat (JS): 1.9MB

Add support for pre-step and post-step logic

Hi!

Looking for thoughts on adding pre-step and post-step events.

Use cases for this include creating custom character and vehicle controllers, where forces should only be applied once per physics step. When not using timestep="vary", applying forces on each frame isn't desirable, as not every frame will step the physics world, some will just interpolate.

This could come in the form of hooks, e.g.

import { usePreStep, usePostStep } from "@react-three/rapier"

usePreStep(() => { /* called before stepping the world */ })
usePostStep(() => { /* called after stepping the world */ })

Or via signals/event handlers on the wrapped rapier World, e.g.

world.onPreStep.add(() => { /* called before stepping the world */ })
world.onPostStep.add(() => { /* called after stepping the world */ })

Or maybe a combination of both.

Keen to hear thoughts, and as usual, happy to help implement ๐Ÿ™‚

a way to prevent children inside rigidbodies to be treated as colliders

currently everything inside a <RigidBody> is being interpreted as a collider, useRigidBody will call:

const autoColliders = colliderSetting !== false ? createCollidersFromChildren(ref.current, rigidBody, {...options, colliders: colliderSetting}, world) : []

which then calls

  object.traverse((child: Object3D | Mesh) => {
    if ("isMesh" in child) {
      ...

the problem is that this makes it hard to add ornamentals to objects, things that are supposed to be grouped with the main physics object so that they may move along, but that should not create a physics shape.

the only workaround i have found is this:

<RigidBody ...>
  <mesh>
    <sphereGeometry />
  </mesh>     
  <mesh traverse={() => null}>
    ...

the second mesh would move with the main object, but tricks rapier so that it skips auto shape. imo this is a super common case and something should probably be introduced to make this more comfortable:

<RigidBody ...>
  <mesh />
  <mesh skip /> // ?
  <mesh userData={{ rapier: { skip: true } }} /> // ?

How to use Motors

From rapier docs, the motor is accessible from the joint creation.

let joint = world.createImpulseJoint(params, body1, body2);
(joint as RAPIER.PrismaticJoint).configureMotorVelocity(1.0, 0.5);

But the useImpulseJoint hook doesn't expose it.

export const useImpulseJoint = <T extends ImpulseJoint>(
body1: MutableRefObject<RapierRigidBody | undefined | null>,
body2: MutableRefObject<RapierRigidBody | undefined | null>,
params: Rapier.JointData
) => {
const { world, RAPIER } = useRapier();
useLayoutEffect(() => {
let joint: T;
if (body1 && body2 && params) {
joint = world.createImpulseJoint(
params,
body1.current!,
body2.current!
) as T;
}
return () => {
if (joint) world.removeImpulseJoint(joint, true);
};
}, [body1, body2]);
};

Question

How to use Motors? Is there another way?
Are there plans to expose the joint from the hooks?

I'd expect something like this:

const joint = useRevoluteJoint(...)

<Box onClick={() => { joint.configureMotorVelocity(1.0, 0.5); }} />

I'd be glad to contribute with a PR.

Issue with Next.js and three MathUtils

I'm using the React-Three-Next starter and just wrap my scene in Index.js with Rapier Physics.

Page.r3f = (props) => (
  <>
    <Suspense>
      <Physics>
        
      </Physics>
    </Suspense>
  </>
)

But it show this error: Cannot find module '...\node_modules\three\src\math\MathUtils'.
I saw the error like this in #88, Is this a bug or I'm just doing something wrong.

rt-rapier breaks with StrictMode enabled

I recently learned that the default <StrictMode> setup that the typical CRA and Vite templates enable by default does not automatically extend into an R3F canvas, and that you need to explicitly create a <StrictMode> node inside <Canvas> to use it. I am now going through all of my own libraries as well as some others that I'm using (like this one) to find out how badly they break with strict mode enabled, and @react-three/rapier unfortunately is one of the things that do break.

To reproduce, just wrap everything inside the <Canvas> in the demo app in a <StrictMode> node.

image

Best way to Lock Rotations?

Heya,

Awesome job on this lib so far, really exciting stuff

Was playing around and wanted to lock an objects rotation, found this seemed to work:
<RigidBody ref={api=>api.raw().lockRotations(true)}>

Is that the best way to do this currently? Was thinking something like this would be more ergonomic but unsure of feasibility / implications:

<RigidBody lockRotations={true}>

Will Rapier run in a WebWorker?

Searching through this code and Rapier docs I was unable to determine if this package would run Rapier in a separate thread/WebWorker, like use-cannon does automatically.

TypeError: Cannot destructure property 'rapier' of 'useRapier(...)' as it is undefined.

Thanks for the great initiative. I just installed the package and I am trying to get it working on a GLTF like so:

    import { RigidBody } from "@react-three/rapier";
    
    <RigidBody colliders={"hull"} restitution={2}>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.perimeter_extrude.geometry}
        material={materials["Material.088"]}
        position={[1180.06, 0, 479.2]}
      />
    </RigidBody>

But getting:

Screen Shot 2022-07-20 at 2 18 22 PM

can someone point me in the right direction please?

thanks!

Gravity change with state

Hello, here's the first of a few issue I found when converting an experiment from cannon to rapier, as asked!

Doesnโ€™t look like I can change the gravity mid render with state. I have a boolean called switchModeOn that alternates on a timer, worked great in cannon to reverse the gravity on the fly!

/src/BoxFan/index.js line 81, switchModeOn should be flipping the gravity.
https://codesandbox.io/s/rapier-physics-box-fan-experiment-debug-gravity-kh45y3?file=/src/BoxFan/index.js

Tracking: Game-readiness of @react-three/rapier

I'm using this ticket as a high-level overview of things that are still missing for the library to be "game-ready". Please ready this merely as a tracking issue, not a wishlist. :-) I will be implementing some of these myself, and submitting them via PR. Also please consider this a living document, I will probably add a thing or two to this list over time.

Must Have:

  • Make update priority configurable. (#78)
  • Clamp deltatime values to 1s max. (#85)
  • Either provide interpolation for translation (currently disabled), or allow the user to configure a varying time step. (Currently, using a lerping follow camera immediately makes the non-interpolated "jumpy" nature of rigidbody movements very obvious.)
  • Allow the user to use sensors. (#90?)
  • Allow setting of restrictRotations and restrictTranslations on rigidbodies. (see rapier docs) This is now enabledRotations and enabledTranslations, and it's already in, yay!

Should Have:

  • Allow for per-collider collision callbacks. (#83)
  • Easy convex collider generation from any geometry, including loaded GLTFs.
  • Allow the user to specify the density of colliders (or a default value on RigidBody.) (Rapier is nice in that it will automatically calculate the mass from the density and the volume of the colliders.)
  • Allow the user to access a rigidbody's scene object through the ref/API. (RigidBodies wrap their children in an <object3D> scene object, but don't expose them through their API. Systems that need to interact with the scene object currently only have the option of establishing their own wrapper object and storing the ref to that separately.)
  • The RigidBodyApi should not force bodies that it interacts with to wake up. This decision should be left to the user (but it would be fine to default this to true.) (#125)
  • The scene object created by RigidBodies and Colliders should default to matrixAutoUpdate={false}, and have their matrices updated explicitly (or this attribute toggled) when the owning rigidbody is awake (ie. not sleeping).
    • Sleeping bodies will never move, so disabling their automatic matrix recalculations is a good optimizations for scenes with larger amounts of rigid bodies (a large portion of which might be sleeping.)
    • It'd be okay to make this optimization opt-in

Nice to Have:

  • Easy to use physics debugger
  • #113

<Physics> doesn't let the user configure the priority for its `useFrame`

As Is:

<Physics> runs its per-frame update look through useFrame, but doesn't let the user specify the priority (useFrame's second argument.)

To Be:

<Physics> should gain a updatePriority prop that lets the user specify the update priority.

Why is this important:

With no way to configure the update priority, useFrame will use a priority if 0. Depending on the project, this can quickly be the wrong update priority. Games in particular will want to update the physics inbetween input and other processing and need immediate control over this.

Expose world API / rapier context to users

Currently there's no way to set a ref for the Physics component. I would expect to be able to pull and manipulate the world ref with
const {world} = useContext(RapierContext);

If a parent object has non-uniform scale, rigid bodies behave strangely

When you have a parent group object that has a non-uniform scale, rigid bodies appear wonky.

For example:

<Canvas>
  <OrbitControls/>

  <React.Suspense>
    <Physics>
      <Debug/>
      <group scale={[2, 0.2, 0.2]}>
        <RigidBody restitution={1} colliders={false}>
          <CuboidCollider
            args={[1, 1, 1]}
          />
          <Box args={[2, 2, 2]}>
            <meshBasicMaterial color={'green'}/>
          </Box>
        </RigidBody>
      </group>
      
      <RigidBody position={[0, -2.5, 0]} type="kinematicPosition">
        <Box args={[20, 0.5, 20]}/>
      </RigidBody>
      <RigidBody position={[1, -2, 0]} type="kinematicPosition">
        <Box args={[1, 0.5, 1]}/>
      </RigidBody>
    </Physics>
  </React.Suspense>
</Canvas>

It looks like this:

Mozilla.Firefox.-.27.October.2022.mp4

However, if we put the scaling operation inside of the rigid body, it seems to work well:

<Canvas>
  <OrbitControls/>

  <React.Suspense>
    <Physics>
      <Debug/>
      <group>
        <RigidBody restitution={1} colliders={false}>
          <group scale={[2, 0.2, 0.2]}>
            <CuboidCollider
              args={[1, 1, 1]}
            />
            <Box args={[2, 2, 2]}>
              <meshBasicMaterial color={'green'}/>
            </Box>
          </group>
        </RigidBody>
      </group>
      
      <RigidBody position={[0, -2.5, 0]} type="kinematicPosition">
        <Box args={[20, 0.5, 20]}/>
      </RigidBody>
      <RigidBody position={[1, -2, 0]} type="kinematicPosition">
        <Box args={[1, 0.5, 1]}/>
      </RigidBody>
    </Physics>
  </React.Suspense>
</Canvas>
Mozilla.Firefox.-.27.October.2022.1.mp4

The issue with this one is that I'm working on a game engine, and we receive the game object's transform through matrices. So to use this workaround, I need to decompose the matrix to translation rotation scale, apply translation and rotation to the parent group, then apply the scale to the child, which is quite complicated and would be best to be prevented. Also, I can totally see others running into this issue if they have scaling of the ancestors of the rigid body.

For me, if a greater ancestor has the scaling (which is possible) it'd be even harder to resolve, if the issue happens there too.

Crash when creating a trimesh collider with non-indexed geometry

It happened using it together with @react-three/csg that creates non-indexed geometries. I'm making a pull request to show how I solved it for me.

Code to reproduce it, also on stackblitz here: https://react-ts-kfoy98.stackblitz.io

import {
  Debug,
  Physics,
  RigidBody,
  InstancedRigidBodies,
  InstancedRigidBodyApi,
  CuboidCollider,
} from '@react-three/rapier';
import { Subtraction, Brush } from '@react-three/csg';
import { Canvas, useFrame } from '@react-three/fiber';
import * as React from 'react';
import './style.css';

function HollowSphere() {
  return (
    <mesh>
      <Subtraction>
        <Brush a>
          <sphereGeometry args={[2]} />
        </Brush>
        <Brush b>
          <sphereGeometry args={[1]} />
        </Brush>
      </Subtraction>
      <meshPhysicalMaterial transmission="0.9" ior="0.97" />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas>
      <Physics>
        <RigidBody colliders="trimesh">
          <HollowSphere />
        </RigidBody>
        <RigidBody>
          <mesh>
            <sphereGeometry args={[0.4]} />
            <meshBasicMaterial color="green" />
          </mesh>
        </RigidBody>
      </Physics>
      <ambientLight />
      <pointLight position={[10, 10, 10]} />
    </Canvas>
  );
}

Lock rotation or lock translation are not working

I tried using lock rotation but it didn't work. I tried enabledRotations prop too but it didn't work either. Am I missing something or is this a bug? Oh and this also applies to lock translation and enabledTranslations too

Debug shows detailed/trimesh geometry instead of hull colliders

Example Code

<Physics>
  <Debug />
  <RigidBody
    colliders={"hull"}
    position={[0, 0, 0]}
  >
    <MiteModel />
  </RigidBody>
</Physics>

Output

See a close fitting detailed/trimesh geometry:

image

Expected

  • I would expect to see a convex hull wrapping the outside of the shape (see below)
  • When other bodies collide with this rigid hull collider body it acts like a hull, not a detailed/trimesh shape.

image


When I have more time I'll whip up a Codesandbox demo.

Add support for contact force events

Documentation

Use Case

When two objects collide, I want to play a sound at the volume appropriate for the collision. eg. softer collisions should be quieter.

Solution

I was expecting to be able to do this from the onCollisionEnter event but it looks like rapier separates colission events and contact force events. With that in mind, would the solution be to add the onContactForce prop to colliders and rigid body's and apply it in the same way other events are within Physics?

I'm happy to add this in, just thought I'd check on the apprach first.

Meshes are not syncing properly with physics objects in latest version

Hello! I ran yarn upgrade on @react-three/rapier today and noticed that it was behaving quite oddly - I made some videos of it on version 0.6.3 (old) and the latest version (new). You can see that the meshes are desyncing from the physics bounds and wobbling around for some reason:

https://www.dropbox.com/scl/fo/99bg75xu9k2o5a08ma8ec/h?dl=0&rlkey=723r3a69flycizmy6oih2w3gh

Assuming it's from here, maybe?
e71ea11#diff-c7ceb0f2f5cdf0d343ec1c8dc97e80c669fa7e755c722586b60caa26b38a8ff6L219

I would make a PR but there seems to be a lot going on in that commit, ...if I get time later I might try to give it a shot :)

Thanks!

Can't configure collision masks and groups for colliders, and active collision types

As Is:

There doesn't seem to be a way to configure collision masks and groups for colliders, or their active collision types.

To Be:

There needs to be a way to configure collision masks and groups for colliders, and active collision types. These are just bitmasks (expressed as numbers), so ideally there should be a supplementary API for creating them without requiring the user to do the math. (In an earlier project, I had a collisions(group, mask) helper that would return the correct numerical value.

Further reading:

Why is this important:

Games need to control exactly which bodies (colliders) are supposed to collide with what other bodies (colliders), and which of them generate collision events.

CuboidCollider nested rotation seems incorrect

Unfortunately rapiers default collision detection is failing my models so I constructed some CuboidColliders in place and used MeshCollider to disable collision on the model. Seems like the relative translation of the parent rotation is off with the CuboidColliders. Or maybe I'm doing this wrong?

https://codesandbox.io/s/rapier-physics-box-fan-experiment-collision-rotation-iptdes?file=/src/BoxFan/FanCollider.js:927-972

๐Ÿ™‡โ€โ™‚๏ธ

[Question] Is there a way to put physic on pause?

I'm currently working on a game engine with React-Three-Fiber and I'm now looking for the best physic engine possible and this one looks great!

I would like to know if there is a way to put the physic on pause (just like use-cannon) which I was not able to find in the documentation.

Thanks!

`RigidBodyProps` is not exported, and incorrect

As Is:

  1. RigidBodyProps is not exported from the package

  2. When used, it still makes TypeScript throw an error where it shouldn't:

image

To Be:

The type should be exported from the library, and it should not cause TypeScript warnings.

Why is this important:

The user might want to encapsulate a RigidBody in a component (like I did in the screenshot), but make it possible for the users of this component to specify the initial position and/or rotation of the body (or override other props.)

`.translation()` does not return the world position systematically

I'm facing an odd behaviour to me:

Description

โœ… OK Case 1: position on <RigidBody>

<RigidBody ref={bodyRef} colliders="ball"
  position={[3, 3, 3]} // ๐Ÿ‘ˆ๐Ÿป `position` on RigidBody
>
  <mesh> 
    <icosahedronGeometry />
    <meshStandardMaterial />
  </mesh>
</RigidBody>
useEffect(() => {
  console.log("translation=", bodyRef.current?.translation());
}, []);

outputs: translation= Vector3 {x: 3, y: 3, z: 3}

image

https://codesandbox.io/s/heuristic-ramanujan-emrms9?file=/src/App.tsx:529-549

โŒ NOK Case 2: position on <mesh>

<RigidBody ref={bodyRef} colliders="ball">
  <mesh
    position={[3, 3, 3]} // ๐Ÿ‘ˆ๐Ÿป `position` on mesh
  >
    <icosahedronGeometry />
    <meshStandardMaterial />
  </mesh>
</RigidBody>
useEffect(() => {
  console.log("translation=", bodyRef.current?.translation());
}, []);

outputs: translation= Vector3 {x: 0, y: 0, z: 0}

image

https://codesandbox.io/s/quirky-microservice-ki7gr7?file=/src/App.tsx:553-573

Expected

The expected behaviour to me, should be .translation() always return 3,3,3 ie. the real, absolute, only one world position of the object

Why object inside a rigid bodies shaking when applying linear velocity?

shaking
So here is an example, can someone explain to me why this is happening and how can I prevent this?
This is a next js version 13 project and I'm using react three fiber version ^8.9.1 and react three rapier ^0.10.0.
here is the code where I move my rigid body:

code
and here is my scene:

scene

RigidBody scaling does not apply to collider scaling

Version: 0.7.1

When I have a RigidBody like this (note the scale prop), I would expect the contained colliders to also scale accordingly:

<RigidBody scale={2}>
  <ConvexHullCollider
    density={3}
    args={[points]} />
</RigidBody>

Colliders appear to respect scaled transforms inside the RigidBody, though, so I can currently sort-of (*) work around this like this:

<RigidBody>
  <group scale={2}>
    <ConvexHullCollider
      density={3}
      args={[points]} />
  </group>
</RigidBody>

However, this has two problems:

  • It's a little confusing because the RigidBody specifically accepts scene object props like position and scale, suggesting that all its contents will be scaled accordingly (which works fine for Three.js scene objects, but not the colliders)
  • In my project, I disable matrixAutoUpdate in some situations for some performance tweaks, but if I do it on the group here, the scale will not be picked up at all. (Speaking of which: the scene objects created by rigidbodies should have their auto matrix calculations disabled, and only performed explicitly for non-sleeping bodies.)

Will there be support on Next.js in the future?

Tried to import anything from rapier, ocurrs error:

Error: Cannot find module '/repo/node_modules/three/examples/jsm/utils/BufferGeometryUtils'

I thinks rapier was trying to get example stuff from three which npm three don't have, hence the error.

Can I tricks rapier to get examples from three-stdlib?

I was running in the latest Next.js, r3f and three.

Support for Rapier 2D

It would be nice to have an option to use Rapider 2D instead of 3D for applications that only need 2D physics, even if they use 3D graphics (think RTS, MOBA games, or 2D platformers with 3D graphics...). The WASM file is 1MB instead of 1.4MB. I suppose <Physics> would need some props to configure the axes to use, and to project onto.

Generate JSDoc!

The readme is getting too thicc. Let's generate a JSDoc from the source!

  • Find a suitable way to do so
  • Choose a theme
  • Ensure source is sufficiently covered in TSDoc comments

How to rotate the wheels on the y axis? (useRevoluteJoint)

https://github.com/pmndrs/react-three-rapier/blob/main/demo/src/examples/car/CarExample.tsx
The current example only shows how to move the car forward and backward

I tried to just turn the wheel a few degrees in the right direction, but got a very strange result

2023-01-20.20-30-27.mp4

Code, Car.tsx:

/* eslint-disable react/no-unknown-property */
import { Cylinder } from "@react-three/drei";
import { GroupProps, useFrame } from "@react-three/fiber";
import {
  RigidBody,
  RigidBodyApi,
  RigidBodyApiRef,
  useRevoluteJoint,
  Vector3Array,
} from "@react-three/rapier";
import { createRef, useEffect, useRef, useState } from "react";
import { Euler, Quaternion } from "three";
import { degToRad } from "three/src/math/MathUtils";

import chasisModel from "./chasis.glb";
import { GLBModel } from "./glb-model";
// import wheelModel from "./wheel.glb"; // not working currently

const WheelJoint = ({
  body,
  wheel,
  bodyAnchor,
  wheelAnchor,
  rotationAxis,
}: {
  body: RigidBodyApiRef;
  wheel: RigidBodyApiRef;
  bodyAnchor: Vector3Array;
  wheelAnchor: Vector3Array;
  rotationAxis: Vector3Array;
}) => {
  useRevoluteJoint(body, wheel, [bodyAnchor, wheelAnchor, rotationAxis]);
  return null;
};

const useControls = () => {
  const [controls, setControls] = useState({
    KeyW: false,
    KeyA: false,
    KeyS: false,
    KeyD: false,
  });

  useEffect(() => {
    const listener = (e: KeyboardEvent) => {
      if (e.repeat) return;
      setControls((old) => ({ ...old, [e.code]: true }));
    };
    document.addEventListener("keydown", listener);
    return () => document.removeEventListener("keydown", listener);
  }, []);

  useEffect(() => {
    const listener = (e: KeyboardEvent) => {
      if (e.repeat) return;
      setControls((old) => ({ ...old, [e.code]: false }));
    };
    document.addEventListener("keyup", listener);
    return () => document.removeEventListener("keyup", listener);
  }, []);

  return controls;
};

export const Car = (props: GroupProps) => {
  const controls = useControls();

  // not working(
  // const wheelPositions: [number, number, number][] = [
  //   [0, -0.5, 0],
  //   [0, -0.5, 0.8],
  //   [-1.2, -0.5, 0],
  //   [-1.2, -0.5, 0.8],
  // ];
  const wheelPositions: [number, number, number][] = [
    [-0.7, 0, 0.7],
    [-0.7, 0, -0.7],
    [0.5, 0, 0.7],
    [0.5, 0, -0.7],
  ];
  const bodyRef = useRef<RigidBodyApi | null>(null);
  const wheelRefs = useRef(wheelPositions.map(() => createRef<RigidBodyApi>()));

  useFrame(() => {
    const speed = controls.KeyW ? 1 : controls.KeyS ? -1 : 0;
    const rotation = controls.KeyA ? 1 : controls.KeyD ? -1 : 0;

    for (const wheel of wheelRefs.current) {
      wheel.current?.applyTorqueImpulse({ x: 0, y: 0, z: speed * 1.5 });
      wheel.current?.setRotation(
        new Quaternion().setFromEuler(new Euler(0, degToRad(rotation * 45), 0))
      );
    }
  });

  return (
    <group {...props}>
      <RigidBody
        angularDamping={1}
        linearDamping={1}
        ref={bodyRef}
        colliders="hull"
        mass={10}
        enabledRotations={[false, false, false]}
      >
        <group
          position={[0, -0.5, 0]}
          rotation={[-1 * (1 / 2) * Math.PI, 0, Math.PI]}
        >
          <GLBModel src={chasisModel} />
        </group>
      </RigidBody>
      {wheelPositions.map((wheelPosition, i) => (
        <>
          <WheelJoint
            key={`wheel-joint-${i}`}
            body={bodyRef}
            wheel={wheelRefs.current[i]}
            bodyAnchor={wheelPosition}
            wheelAnchor={[0, 0, 0]}
            rotationAxis={[0, 0, 1]}
          />
          <RigidBody
            mass={20}
            position={wheelPosition}
            ref={wheelRefs.current[i]}
            colliders="hull"
            key={`wheel-rigidbody-${i}`}
          >
            <Cylinder
              rotation={[Math.PI / 2, 0, 0]}
              args={[0.25, 0.25, 0.2, 32]}
              castShadow
              receiveShadow
            >
              <meshPhysicalMaterial />
            </Cylinder>
            {/* 
            not working, messy movement
            <group rotation={[-Math.PI / 2, 0, 0]}>
              <GLBModel src={wheelModel} />
            </group> */}
          </RigidBody>
        </>
      ))}
    </group>
  );
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.