notrabs / use-ammojs

ammo.js physics for use with react-three-fiber
MIT License
47 stars 9 forks source link
ammojs physics react react-three-fiber threejs wasm webworker

npm npm npm

use-ammojs

Fast Physics hooks for use with react-three-fiber.

Achieved by running the ammo.js physics library in a web-worker. Ammo itself is a WebAssembly wrapper around the powerful Bullet Physics engine. Data is synced with SharedArrayBuffers having minimal impact on the main thread.

yarn add use-ammojs
npm i use-ammojs

Built on top of three-ammo and its related work.

Examples

API Demos

Stress Tests

⚠️ Note that the codesandbox examples do not support SharedArrayBuffers due to missing cross-origin isolation and use regular ArrayBuffers as a fallback. Currently the debug-drawer has no ArrayBuffer fallback implemented and will not render anything.

Why not use use-cannon instead?

use-cannon is great and a inspiration for this package, but it is missing features like soft-bodies and lacks performance in scenes with large triangle meshes. ammo.js is a direct wrapper around the powerful Bullet Physics engine, which solves these problems.

At the time of writing however use-cannon is more mature and great for most projects.

Roadmap

Main goals:

Low priority goals (for unchecked tasks):

Quick Start

1. Wrap your scene in a Physics Provider

import { Physics } from "use-ammojs";

<Physics drawDebug>[...]</Physics>;

2.a Make objects physical (Rigid Bodies)

Automatically parse Shape parameters from the three Mesh (courtesy of three-to-ammo):

import { Box } from "@react-three/drei";
import { useRigidBody, ShapeType } from "use-ammojs";
import { Mesh } from "three";

function MyBox() {
  // If you need a ref with a narrower type than Object3D, provide a generic argument here
  const [ref] = useRigidBody<Mesh>(() => ({
    mass: 1,
    position: [0, 2, 4],
    shapeType: ShapeType.BOX,
  }));

  return (
    <Box ref={ref}>
      <meshBasicMaterial attach="material" color="red" />
    </Box>
  );
}

or define Collision Shapes manually:

const [playerCapsuleRef] = useRigidBody(() => ({
  bodyType: BodyType.DYNAMIC,
  shapeType: ShapeType.CAPSULE,
  angularFactor: new Vector3(0, 0, 0),
  shapeConfig: {
    fit: ShapeFit.MANUAL,
    halfExtents: new Vector3(0.3, 0.6, 0.3),
  },
}));

or add collisions to an imported gltf scene:

useRigidBody(
  () => ({
    shapeType: ShapeType.MESH,
    bodyType: BodyType.STATIC,
  }),
  gltf.scene
);

2.a Make objects squishy (Soft Bodies)

const [ref] = useSoftBody(() => ({
  type: SoftBodyType.TRIMESH,
}));

return (
  <Sphere position={[0, 2, 7]} args={[1, 16, 16]} ref={ref}>
    <meshPhysicalMaterial attach="material" color="blue" />
  </Sphere>
);

2.c Add Constraints

TODO;

3.a Raycasts

const { rayTest } = useAmmo();

[...]

const hits = await rayTest({
  from: new Vector3(0, 5, 7),
  to: new Vector3(0, -1, 7),
  multiple: true
})

if (hits.length) {
    console.log(hits[0].object.name, hits[0].hitPosition)
}

3.b Update Motion State

const [playerRef, api] = useRigidBody(() => ({
  bodyType: BodyType.DYNAMIC,
  shapeType: ShapeType.CAPSULE,
  angularFactor: new Vector3(0, 0, 0),
  shapeConfig: {
    fit: ShapeFit.MANUAL,
    halfExtents: new Vector3(0.3, 0.6, 0.3),
  },
}));

function handleRespawn() {
  api.setPosition(new Vector3(0, 0, 0));
  api.setLinearVelocity(new Vector3(0, 0, 0));
}

Documentation

Components

<Physics />

Phyiscs Context. Use to wrap all physical objects within the same physics world.

<PhysicsStats />

Shows a stats.js panel with physics timing info. Use within a <Physics /> Context

Hooks

const { rayTest } = useAmmo();

Utility funcionts available anywhere in the <Physics /> context.

const [ref, api] = useRigidBody();
const [ref, api] = useSoftBody();

Cross-origin isolation

To use SharedArrayBuffers for better communication between the main-thread and the web-worker-thread, a cross-origin isolated environment is necessary in modern browsers. This requires sending the following HTTP headers in the response of the main html document (Learn more):

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

use-ammojs will fallback to using ArrayBuffers and postMessage() transfers if SharedArrayBuffers are not available. This is not as bad as a full copy on each transfer, but it does not allow the data to be availble on both threads at the same time.

Developing locally using use-ammojs

Setting up react-scripts to work with yarn link using @craco/craco 1. `yarn add @craco/craco --dev` 2. Replace `react-scripts` with `craco` in your `package.json` (see [@craco/craco](https://www.npmjs.com/package/@craco/craco) documentation) 3. Add `craco.config.js` to project root: ```js const path = require("path"); module.exports = { webpack: { configure: (webpackConfig) => { // Fix that prevents a duplicate react library being imported when using a linked yarn package webpackConfig.resolve.alias = { ...webpackConfig.resolve.alias, react: path.resolve("./node_modules/react"), "@react-three/fiber": path.resolve("./node_modules/@react-three/fiber"), three: path.resolve("./node_modules/three"), }; return webpackConfig; }, }, // Make sure SharedArrayBuffers are available locally devServer: { headers: { "Cross-Origin-Embedder-Policy": "require-corp", "Cross-Origin-Opener-Policy": "same-origin", }, }, }; ```
  1. Run yarn link in use-ammojs root directory
  2. Run yarn link use-ammojs in your project's directory
  3. Run yarn start in use-ammojs to start the development bundler
  4. Build and run your project as usual