mcpField
P
physics

physics

github.com/drawcall-aiDevelopment160 installs
Repository

About physics

A BVH-based physics skill for developers building interactive scenes with @pmndrs/viverse. It provides static and kinematic geometry, a character controller, and sensors, while leaving gameplay rules to the project.

Key features

  1. 01Create a BVH physics world
  2. 02Add static and kinematic bodies
  3. 03Control character velocity and impulses
  4. 04Check character grounded state
  5. 05Detect sensor entry and exit

Use cases

  1. 01Add collision to scene geometry
  2. 02Build character movement and jump behavior
  3. 03Respond to characters entering or leaving areas

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/drawcall-ai/skills --skill physics

After installing, type/physicsin Claude Code to confirm it loaded.

Details

From the project README

Physics

Physics using BVH (Bounding Volume Hierarchy) for static/kinematic geometry. Dynamic bodies cannot interact with each other yet.

Physics World

Creating a physics world

import { BvhPhysicsWorld } from '@pmndrs/viverse'

const world = new BvhPhysicsWorld()

addBody(object, kinematic) — the second argument is whether the body is kinematic (script-moved, like a platform or train), not whether it is static. Pass false for static world geometry (ground, walls), true for things you move yourself.

world.addBody(ground, false) // static
world.addBody(train, true)   // kinematic (you move it each frame)

Watch the inversion: addBody's flag is kinematic, but addSensor's second flag below is isStatic — the booleans mean opposite things.

Character Controller

BvhCharacterPhysics is the character controller — it moves and collides the character body. It does not animate the character and is not a camera. For a full controllable humanoid, compose it with CharacterCameraBehavior (camera) and Acta for all animation and the motion it produces (see the acta skill). Do not use SimpleCharacter — it bundles its own animation that conflicts with Acta.

Creating a character controller

import { BvhCharacterPhysics, type BvhCharacterPhysicsOptions } from '@pmndrs/viverse'

const characterPhysics = new BvhCharacterPhysics(world)

The controller assumes the character object's origin sits at its feet (the bottom of the capsule). If your model's origin is at its center or head, the character floats or sinks — wrap it in a Group whose origin is at the feet. Tuning lives in the optional BvhCharacterPhysicsOptions (third arg to update): capsuleRadius, capsuleHeight, gravity, maxGroundSlope, updatesPerSecond, linearDamping.

Driving it

characterPhysics.inputVelocity.copy(velocity) // continuous (walking) — set each frame
characterPhysics.applyVelocity(jumpImpulse) // one-off impulse (e.g. jumping)

For an Acta character the path of information is input → Acta → physics: you pass Acta a movement intent (a camera-relative moveDirection, length 0..1), Acta turns the active animation into a desired velocity and hands it to you in its motion callback, and you set inputVelocity from that velocity and step the controller there. Do not set inputVelocity from raw input, and do not move the character anywhere else. See the acta skill.

Updating the controller

Call every frame after setting velocity:

characterPhysics.update(character, delta) // pass a BvhCharacterPhysicsOptions third arg to tune
Ground State
characterPhysics.isGrounded // true when character is on the ground
What the controller does not do — build these yourself

The controller gives you exactly two primitives — inputVelocity (continuous) and applyVelocity (impulse) — plus isGrounded. The rules a specific game layers on top are yours to write:

  • Multi-jump / double-jump, coyote-time, jump buffering: there is no jump counter — gate the first applyVelocity on isGrounded, track your own air-jump count, and reset it when grounded.
  • Wall-jump / wall-slide: isGrounded reports ground only, not walls. Detect a wall with a horizontal world.raycast(ray, far) from the capsule — the returned intersection gives the hit distance and surface normal — then applyVelocity away-from-wall + up.
  • Moving-platform ride-along: standing on a kinematic platform does not carry the character. Add the platform's per-frame delta to the character yourself while it stands on it.
  • Falling off the world / out of bounds: isGrounded is true only when the capsule's downward shapecast hits collision geometry — past the edge of a finite ground mesh there is nothing to hit, so the character falls forever and stays stuck in its airborne/fall pose (which often reads as an unintended "flying" or glide state). The controller will not stop this. Keep the player on solid ground yourself: clamp position to the collidable area, add an out-of-bounds floor plane or a kill-barrier that respawns, rather than assuming the world is infinite. Bound the player to the physics mesh, which may extend past the visible play area.

These are gameplay rules, not missing features — the controller stays deliberately minimal so each game tunes its own feel.

Sensors

Sensors detect when the character enters or exits a volume.

Adding a sensor

world.addSensor(object, isStatic, (intersected) => {
  if (intersected) {
    // character entered the sensor
  } else {
    // character exited the sensor
  }
})

Removing a sensor

world.removeSensor(object)

If you prefer a single callback that provides the sensor object that the character entered, do the following

function onEnter(object: Object3D) {}

world.addSensor(obj1, isStatic, (intersected) => intersected && onEnter(obj1))
world.addSensor(obj2, isStatic, (intersected) => intersected && onEnter(obj2))

Comments

Markdown supported