Skip to content

Multithreaded Ammo.js Glue for React Three Fiber. A zero-config, high-performance wrapper for Ammo.js.

Notifications You must be signed in to change notification settings

HyperExtendedReality/r3f-ammo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

npm npm

r3f-ammo

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.

r3f-ammo is a modern fork of use-ammojs with added features and compatibility fixes for Next.js, Turbopack, and the modern @react-three/drei ecosystem.

Installation

pnpm install r3f-ammo

or

bun add r3f-ammo

Features

  • WebWorker Physics: Heavy calculations run off the main thread
  • Soft Body Textures: Added support for textures on soft bodies ✨
  • Modern Stats: Integrated with Leva and StatsGl for deep performance profiling
  • Next.js Compatible: Fixed bundling issues with Turbopack and Server Components
  • Full Soft Body Support: Cloth, ropes, and volumes

Examples

API Demos (Sandbox)

Note: Examples below are from the original use-ammojs repo but remain compatible with the r3f-ammo API.

⚠️ Note: 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 r3f-ammo?

This package builds upon use-ammojs and use-cannon. While use-cannon is excellent and mature, it lacks Soft Body support and can struggle with large triangle meshes. r3f-ammo brings the full power of Bullet Physics (Ammo.js) to React Three Fiber with modern tooling support.

Key Improvements over use-ammojs

  • âś… Texture support for Soft Bodies (main feature addition)
  • âś… Next.js and Turbopack compatibility
  • âś… Modern build system using Vite
  • âś… Enhanced performance monitoring with Leva integration
  • âś… TypeScript improvements
  • âś… Updated dependencies and ecosystem compatibility

Roadmap

Main Goals

  • Create a Physics World as a React context and simulate it in a web-worker
  • Sync three objects to physics Rigid Bodies
  • Add Rigid Body support
  • Add Soft Body support
    • Volumes/Cloth from Triangle Mesh
    • Ropes
    • Support textures on Soft Bodies ✨ (New in r3f-ammo)
    • Deformables
  • Add Constraints between Rigid Bodies
  • Add Constraints to Soft Bodies (ability to pin nodes in place or to Rigid Bodies)
  • Improve Physics API
    • Make all props reactive
    • Expose more methods through the hook (e.g. setPosition/applyImpulse/more...)
    • Support collision callbacks
  • Modernize Build System (Vite/Next.js/Turbopack compatibility) ✨

Low Priority Goals

  • Automatic refresh rate detection and performance throttling
  • Add Raycast queries
    • One-time (async) ray-tests
    • Continuous queries through a fixed scene component to mitigate worker latency
  • Use ArrayBuffers as a fallback for missing cross-origin isolation
    • Rigid Bodies
    • Soft Bodies
    • Debug Rendering
  • Simulation management
    • Configurable simulation speed
    • Expose performance info
    • Integrated with @react-three/drei StatsGl component
    • Integrated with Leva for graphing
    • Automatically pause simulation if tab is out of focus or not rendering (as option)
  • Improve automatic shape detection (set shapeType automatically based on the three Mesh type)
  • Raycast Vehicle API
  • Support for instanced objects

Quick Start

1. Wrap your scene in a Physics Provider

import { Physics, PhysicsStats } from "r3f-ammo";

<Physics drawDebug>
  <Scene />
  {/* Optional: Add performance visualizer */}
  <PhysicsStats showGraph showStats />
</Physics>;

2a. 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 "r3f-ammo";
import { Mesh } from "three";

function MyBox() {
  // If you need a ref with a narrower type than Object3D, provide a generic argument
  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:

import { BodyType, ShapeType, ShapeFit } from "r3f-ammo";
import { Vector3 } from "three";

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
);

2b. Make objects squishy (Soft Bodies)

import { useSoftBody, SoftBodyType } from "r3f-ammo";
import { Sphere } from "@react-three/drei";

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

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

2c. Add Constraints

// TODO: Add constraint examples

3. Raycasts

import { useAmmo } from "r3f-ammo";
import { Vector3 } from "three";

function RaycastExample() {
  const { rayTest } = useAmmo();

  const performRaycast = async () => {
    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);
    }
  };

  return <button onClick={performRaycast}>Cast Ray</button>;
}

4. 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() {
  // Directly set position/velocity via the API
  api.setPosition(new Vector3(0, 0, 0));
  api.setLinearVelocity(new Vector3(0, 0, 0));
}

Documentation

Components

<Physics />

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

Props:

  • drawDebug?: boolean - Enable debug rendering
<Physics drawDebug>{/* Your physics objects */}</Physics>

<PhysicsStats />

Shows a StatsGl panel (FPS/GPU) and registers a Leva monitor graph for Physics CPU timings.

Props:

  • showGraph?: boolean - Show Leva performance graph (default: true)
  • showStats?: boolean - Show StatsGl panel (default: true)
<PhysicsStats showGraph showStats />

Hooks

useAmmo()

Utility functions available anywhere within a <Physics /> context.

const { rayTest } = useAmmo();

useRigidBody()

Create a rigid body physics object.

const [ref, api] = useRigidBody(() => ({
  mass: 1,
  position: [0, 5, 0],
  shapeType: ShapeType.BOX,
}));

Returns:

  • ref: Reference to attach to your Three.js object
  • api: Methods to control the body (setPosition, setLinearVelocity, etc.)

useSoftBody()

Create a soft body physics object (cloth, rope, volume).

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

Cross-Origin Isolation

To use SharedArrayBuffers for optimal communication between the main thread and web worker, cross-origin isolation is required in modern browsers.

Next.js Config (next.config.js)

module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
          { key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
        ],
      },
    ];
  },
};

Vite Config (vite.config.js)

import { defineConfig } from "vite";

export default defineConfig({
  server: {
    headers: {
      "Cross-Origin-Embedder-Policy": "require-corp",
      "Cross-Origin-Opener-Policy": "same-origin",
    },
  },
});

Create React App (with @craco/craco)

Click to expand CRA configuration
  1. Install craco: npm install @craco/craco --save-dev
  2. Replace react-scripts with craco in your package.json scripts
  3. Create craco.config.js in project root:
const path = require("path");

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      // Fix duplicate React instances when using yarn/npm link
      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;
    },
  },
  devServer: {
    headers: {
      "Cross-Origin-Embedder-Policy": "require-corp",
      "Cross-Origin-Opener-Policy": "same-origin",
    },
  },
};

Fallback Behavior

r3f-ammo will automatically fallback to using ArrayBuffers with postMessage() transfers if SharedArrayBuffers are not available. While not as performant as SharedArrayBuffers, this fallback still avoids full data copies on each transfer.

Developing Locally

Setting up local development with Next.js/Vite

Using npm link

  1. Run npm link in r3f-ammo root directory
  2. Run npm link r3f-ammo in your project's directory
  3. In r3f-ammo, run npm run build or npm run dev to watch for changes
  4. Build and run your project as usual

Resolving Peer Dependency Issues (Next.js)

If using Next.js with npm link, you may need to resolve peer dependency duplications:

// next.config.js
const path = require("path");

module.exports = {
  webpack: (config) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      react: path.resolve(__dirname, "node_modules/react"),
      three: path.resolve(__dirname, "node_modules/three"),
      "@react-three/fiber": path.resolve(
        __dirname,
        "node_modules/@react-three/fiber"
      ),
    };
    return config;
  },
};

Migration from use-ammojs

r3f-ammo maintains API compatibility with use-ammojs. To migrate:

  1. Replace use-ammojs with r3f-ammo in your package.json

  2. Update imports:

    // Before
    import { Physics, useRigidBody } from "use-ammojs";
    
    // After
    import { Physics, useRigidBody } from "r3f-ammo";
  3. Update <PhysicsStats /> if used - it now integrates with Leva and StatsGl

  4. Ensure cross-origin isolation headers are configured for your build tool

Built With

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Acknowledgments

  • Original use-ammojs by the Poimandres team
  • three-ammo and related work
  • The Bullet Physics and Ammo.js communities

About

Multithreaded Ammo.js Glue for React Three Fiber. A zero-config, high-performance wrapper for Ammo.js.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published