@react-three/p2

repository·main·Indexed 41 days ago

https://github.com/pmndrs/use-p2

A set of 2D physics-based React hooks for react-three-fiber, providing a bridge to the p2-es physics engine. It includes a <Physics /> provider, shape hooks like useBox, useCircle, and usePlane, and a <Debug /> component for visualization. Version 0.0.6 features support for Dynamic, Static, and Kinematic body types, raycasting hooks, and specialized controllers such as KinematicCharacterController and PlatformController.

Tokens
8.1K
Snippets
25
Records
35
Agent score
51%

What's inside @react-three/p2

  1. Configure Broadphase algorithms

    main

    The broadphase setting in the Physics component determines how potential collisions are identified:

    • 'Naive': Checks every body against every other body. This is simple but results in the maximum number of narrowphase checks (high CPU cost in complex scenes).
    • 'SAP' (Sweep and Prune): Sorts bodies along an axis and finds pairs by checking body size and position. This is generally more efficient. You can control the sorting axis using the axisIndex property.
  2. Understand body types: Dynamic, Static, and Kinematic

    main

    When defining a body via BodyProps, you can specify its type:

    • Dynamic: Fully simulated. Moves according to forces and reacts to collisions. Always has finite, non-zero mass.
    • Static: Does not move during simulation and behaves as if it has infinite mass. Can be moved manually by setting position, but its velocity is always zero. Static bodies do not collide with other static or kinematic bodies.
    • Kinematic: Moves according to its velocity but does not respond to forces. Behaves as if it has infinite mass. Kinematic bodies do not collide with other static or kinematic bodies.
  3. How @react-three/p2 works

    main

    To implement physics in your React Three Fiber scene, follow these steps:

    1. Import components: Import Physics and specific shape hooks (e.g., useBox) from @react-three/p2.
    2. Create a Physics world: Wrap your physics-related objects in the <Physics /> component. Use the normalIndex prop to define the world's rotation: use 1 for top-down applications and 2 for side-scroller applications.
    3. Define shapes: Use a shape hook (like useBox, useCircle, etc.) to define the contact surface and mass. This hook returns a ref and an api object.
    4. Bind to meshes: Attach the returned ref to a Three.js object (like a <mesh />) to make it subject to physics.
    5. Interact via API: Use the returned api to apply forces, velocities, or positions.
    6. Subscribe to updates: Use the api to subscribe to property changes (like velocity) for frame-by-frame updates.
    import { Physics, useBox, ... } from '@react-three/p2'
    
    // 1. Setup Physics World
    <Physics normalIndex={2}>
      {/* 2. Define Shape and get ref/api */}
      {/* const [ref, api] = useBox(() => ({ mass: 1 })) */}
      
      {/* 3. Bind to Mesh */}
      <mesh ref={ref} />
    </Physics>
  4. Debug your physics scene

    main

    You can visualize how the physics engine perceives your scene using the Debug component. Ensure that the normalIndex passed to Debug matches the normalIndex passed to the Physics component.

    import { Physics, Debug } from '@react-three/p2'
    
    ReactDOM.render(
      <Canvas>
        <Physics normalIndex={2}>
          <Debug color="black" scale={1.1} linewidth={0.01} normalIndex={2}>
            {/* children */}
          </Debug>
        </Physics>
      </Canvas>,
      document.getElementById('root'),
    )
  5. Build and run @react-three/p2-examples

    main

    The @react-three/p2-examples package requires the parent project (@react-three/p2) to be built first because it relies on an alias pointing to ../dist/index.

    Follow these steps to set up the development environment:

    1. Build the parent project from the root directory:
      • Navigate to the parent directory.
      • Install dependencies and build.
      • Navigate back to the examples directory and install dependencies.
    2. Start the development server from the examples directory.
    3. View the examples by visiting http://localhost:3000 in your browser.
    # Build the parent project
    cd ..
    yarn
    npm run build
    cd examples
    yarn
    
    # Start the dev server
    npm run dev
  6. Subscribe to physics property changes

    main

    You can subscribe to changes in specific body properties or vectors. The available subscription names are categorized into atomicNames and vectorNames.

    Atomic Properties (atomicNames): allowSleep, angle, angularDamping, angularVelocity, collisionFilterGroup, collisionFilterMask, collisionResponse, fixedRotation, isTrigger, linearDamping, mass, material, sleepSpeedLimit, sleepTimeLimit, userData.

    Vector Properties (vectorNames): position, velocity.

    Other Subscriptions: collisions, raysData.

    Subscriptions are managed via subscribe and unsubscribe messages sent to the worker.

    export const atomicNames = [
      'allowSleep',
      'angle',
      'angularDamping',
      'angularVelocity',
      'collisionFilterGroup',
      'collisionFilterMask',
      'collisionResponse',
      'fixedRotation',
      'isTrigger',
      'linearDamping',
      'mass',
      'material',
      'sleepSpeedLimit',
      'sleepTimeLimit',
      'userData',
    ] as const
    
    export const vectorNames = ['position', 'velocity'] as const
    
    export const subscriptionNames = [...atomicNames, ...vectorNames, 'collisions', 'raysData'] as const
  7. Handle collision and raycast events

    main

    The physics engine emits several types of events that can be handled:

    • collide: A continuous collision event containing contact points, normals, and impact velocity.
    • collideBegin: Triggered when two bodies first make contact.
    • collideEnd: Triggered when two bodies stop making contact.
    • rayhit: Triggered when a raycast intersects a body, providing distance, hit point, and normal.
    export type CollideBeginEvent = {
      body: Object3D
      op: 'event'
      target: Object3D
      type: 'collideBegin'
    }
    
    export type CollideEndEvent = {
      body: Object3D
      op: 'event'
      target: Object3D
      type: 'collideEnd'
    }
    
    export type CollideEvent = Omit<WorkerCollideEvent['data'], 'body' | 'target' | 'contact'> & {
      body: Object3D
      contact: Omit<WorkerContact, 'bi' | 'bj'> & { bi: Object3D; bj: Object3D }
      target: Object3D
    }
    
    export type RayhitEvent = Omit<WorkerRayhitEvent['data'], 'body'> & { body: Object3D | null }
  8. Configure the physics simulation with the Provider component

    main

    The Provider component is the root of the physics simulation. It initializes a CannonWorkerAPI to run physics calculations in a web worker, offloading the heavy computation from the main thread. It manages the simulation loop via useFrame and synchronizes physics bodies with Three.js objects (including InstancedMesh).

    Wrap your @react-three/fiber scene in the Provider to enable physics. You can configure gravity, solver iterations, broadphase algorithms, and stepping behavior through its props.

    import { Provider } from '@react-three/p2'
    
    function App() {
      return (
        <Canvas>
          <Provider 
            gravity={[0, -9.81, 0]} 
            iterations={10} 
            solver="GS"
          >
            {/* Your physics objects here */}
          </Provider>
        </Canvas>
      )
    }
  9. Simple physics example: Ball falling on a box

    main

    A complete example demonstrating a static box and a dynamic ball using useBox and useCircle.

    import { Canvas } from '@react-three/fiber'
    import { Physics, useBox, useCircle } from '@react-three/p2'
    
    function Box() {
      const [ref] = useBox(() => ({ mass: 0, position: [0, -2] }))
      return (
        <mesh ref={ref}>
          <boxGeometry />
        </mesh>
      )
    }
    
    function Ball() {
      const [ref] = useCircle(() => ({ mass: 1, position: [0, 2] }))
      return (
        <mesh ref={ref}>
          <sphereGeometry />
        </mesh>
      )
    }
    
    ReactDOM.render(
      <Canvas>
        <Physics normalIndex={2}>
          <Box />
          <Ball />
        </Physics>
      </Canvas>,
      document.getElementById('root'),
    )
  10. Interact with physics bodies using the PublicApi

    main

    The PublicApi returned by body hooks allows you to manipulate the physics body during runtime.

    Core Methods:

    • applyForce(force, worldPoint): Applies a force at a specific world position.
    • applyImpulse(impulse, worldPoint): Applies an impulse at a specific world position.
    • applyLocalForce(force, localPoint): Applies a force relative to the body's local coordinate system.
    • applyLocalImpulse(impulse, localPoint): Applies an impulse relative to the body's local coordinate system.
    • applyTorque(torque): Applies torque to the body.
    • sleep() / wakeUp(): Manually manage the body's sleep state.

    Atomic Properties (via .at(index) or direct access): You can use .set(value) and .subscribe(callback) to manage properties like mass, linearDamping, angularDamping, allowSleep, and collisionGroup.

    // Example: Applying an impulse to a body
    api.applyImpulse([0, 10, 0], [0, 0, 0]);
    
    // Example: Subscribing to velocity changes
    api.velocity.subscribe((v) => console.log('Velocity:', v));
    
    // Example: Setting mass
    api.at('mass').set(5);
  11. Perform raycasting with useRaycast hooks

    main

    Use these hooks to perform raycasting within the physics world:

    • useRaycastClosest(options, callback, deps): Triggers when the closest object is hit.
    • useRaycastAny(options, callback, deps): Triggers when any object is hit.
    • useRaycastAll(options, callback, deps): Triggers for all objects hit by the ray.

    The callback receives a RayhitEvent containing the body and target objects hit.