Physics
new Physics(config : Object)Physics - Main Thread Physics Engine Interface
High-level API for initializing and controlling the physics engine from the main thread. Manages SharedArrayBuffer creation, worker lifecycle, and provides convenient accessors for physics state.
Architecture
The Physics acts as a facade between the main thread and the physics worker:
SharedArrayBuffer Communication
All physics state is synchronized via typed arrays backed by SharedArrayBuffer.
Use the body reference from addBody() for state access:
// Store body reference from addBody()
const playerApi = await physics.addBody(BodyType.DYNAMIC, playerMesh);
// Sync physics to mesh with interpolation
playerApi.body.sync(alpha);
// Direct buffer access via body.view (advanced)
const view = playerApi.body.view;
console.log(view.positionx, view.positiony, view.positionz);
Basic Usage
import { Physics, PhysicsController, BodyType } from '@three-blocks/pro';
import * as THREE from 'three/webgpu';
const physics = new Physics({ gravity: -9.81, debug: false });
await physics.init();
// Add bodies - STORE the api reference
const playerApi = await physics.addBody(BodyType.DYNAMIC, playerMesh);
await physics.addBody(BodyType.STATIC, groundMesh);
// Start simulation
await physics.start();
// Game loop - use body.sync() for mesh synchronization
const controller = new PhysicsController(physics);
function animate() {
controller.update(delta);
playerApi.body.sync(controller.getInterpolationAlpha());
renderer.render(scene, camera);
}
Events (Body-Level)
import { BodyEvent } from '@three-blocks/pro';
// Listen for collision events on a body
const api = await physics.addBody(BodyType.STATIC, wallMesh);
api.body.addEventListener(BodyEvent.COLLIDE, (event) => {
console.log('Wall collision detected');
});
// Listen for zone triggers
const zone = await physics.addBody(BodyType.ZONE, triggerMesh);
zone.body.addEventListener(BodyEvent.ZONE_ENTER, (event) => {
console.log('Player entered zone');
});
zone.body.addEventListener(BodyEvent.ZONE_LEAVE, (event) => {
console.log('Player left zone');
});
// Listen for weapon hits (hitscan, projectile, or hybrid)
const npc = await physics.addBody(BodyType.KINEMATIC, npcMesh);
npc.body.addEventListener(BodyEvent.HIT, (event) => {
console.log('NPC was hit!', event.hitPoint, event.shooterIndex);
});
Weapons System (Hitscan, Projectile, Hybrid)
import { WeaponType } from '@three-blocks/pro';
// Hitscan (instant raycast) - rifles, pistols, lasers
physics.weapons.fireFromCamera({
type: WeaponType.HITSCAN,
shooterIndex: 0,
shooterType: 'dynamic',
maxDistance: 100,
});
// Projectile (traveling with physics) - arrows, grenades, rockets
physics.weapons.fire(towerIndex, {
type: WeaponType.PROJECTILE,
bodyType: 'kinematic',
direction: [0, 0, 1],
speed: 40,
gravity: 1,
});
// Hybrid (instant hit + visual projectile) - competitive FPS feel
physics.weapons.fireFromCamera({
type: WeaponType.HYBRID,
shooterIndex: 0,
damage: 25,
});
Constructor Parameters
configoptionalObjectDefault is
{}.gravityoptionalnumberGravity scalar.
Default is-9.81.linearDragoptionalnumberLinear drag coefficient.
Default is1.maxDynamicBodiesoptionalnumberMaximum dynamic bodies.
Default is16.maxKinematicBodiesoptionalnumberMaximum kinematic bodies.
Default is256.maxStaticBodiesoptionalnumberMaximum static bodies.
Default is512.maxZonesoptionalnumberMaximum zone triggers.
Default is128.debugoptionalbooleanEnable debug mode.
Default isfalse.physicsFPSoptionalnumberPhysics update rate in FPS (can be overridden by physics model's targetFPS).
Default is60.tickRateoptionalnumberInternal physics tick rate in Hz (e.g., 100 for low fidelity, 625 for high fidelity).
Default is625.physicsModeloptionalstringPhysics model preset ('arcade', 'platformer', 'realistic').
Default is'arcade'.customPhysicsModeloptionalObjectCustom physics model object implementing PhysicsModel interface.
Default isnull.solverIterationsoptionalnumberNumber of velocity solver iterations for D↔D collisions (more = stable stacks, slower).
Default is4.positionIterationsoptionalnumberNumber of position correction iterations for D↔D collisions.
Default is2.
See also
- {@link physicsState} - Worker-side state container
- {@link PhysicsController} - High-level player controller
Example
┌─────────────────────────────────────────────────────────────────┐
│ MAIN THREAD │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────────────┐ │
│ │ Your Game │───▶│ Physics │◀──▶│ SharedArrayBuffer │ │
│ │ Code │ │ (facade) │ │ Views │ │
│ └─────────────┘ └──────┬──────┘ └───────────────────┘ │
│ │ Comlink RPC │
└────────────────────────────┼────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHYSICS WORKER (60 FPS) │
│ - Collision detection (BVH raycasting) │
│ - Character controller (capsule-based) │
│ - Camera system (3rd person, 1st person, bird view) │
│ - Trigger zones and events │
└─────────────────────────────────────────────────────────────────┘
Properties
# .weapons : PhysicsWeapons|null
Unified weapon system. Supports hitscan, projectile, and hybrid weapons with a single API.
# .physicsState :
Get the global physics state view.
# .cameraState :
Get the camera state view.
# .gravity :
Get the current gravity scalar value. Negative values pull downward (e.g., -9.81 for Earth gravity).
# .player :
Get the main player body buffer view (dynamic body at index 0).
Note: Prefer using the body reference from addBody() instead:
playerApi.body.sync(alpha) for mesh sync, playerApi.body.view for buffer access.
# .sharedKinematic :
Get the shared kinematic body views array. Used by KinematicAnimator to write animation values to SharedArrayBuffer.
# .syncView :
Get the sync buffer view for Atomics-based synchronization.
# .physicsApi :
Get the physics API for direct calls. Returns a proxied API that automatically registers static meshes for debug visualization.
# .api :
Alias for physicsApi - provides cleaner access via physics.api
# .workerApi :
Legacy alias for physicsApi - for backwards compatibility
# .staticMeshes :
Get all static meshes array.
Methods
getInterpolationAlpha#
getInterpolationAlpha() : numberGet interpolation alpha, cached per frame. All consumers (cameras, body.sync, controllers) get identical value within same frame. This prevents timing mismatches when different components calculate alpha at different times.
Returns
number — Interpolation factor (0-1)init#
init(workerInstance : Worker) : Promise<void>Initialize the physics engine. Creates SharedArrayBuffers and initializes the worker.
Parameters
workerInstanceoptionalWorkerDefault is
null.Returns
Promise<void>start#
start() : Promise<string>Start the physics simulation.
Returns
Promise<string> — Status messagestop#
stop() : Promise<string>Stop the physics simulation.
Returns
Promise<string> — Status messagepause#
pause()Pause the physics simulation.
resume#
resume()Resume the physics simulation.
getView#
getView(bodyType : string) : ObjectGet raw buffer view for a body type.
Internal: Prefer using the body reference from addBody() instead.
Use api.body.view for direct buffer access, or api.body.sync(alpha)
for mesh synchronization.
Parameters
bodyTypestringReturns
Object — Buffer viewgetDynamicBody#
getDynamicBody(index : number) : Object|nullGet a dynamic body buffer view by index.
Internal: Prefer using the body reference from addBody() instead.
Store the api from addBody() and use api.body.sync(alpha) or api.body.view.
Parameters
indexnumberReturns
Object | null — Body buffer view or null if not foundgetKinematicBody#
getKinematicBody(index : number) : Object|nullGet a kinematic body buffer view by index.
Internal: Prefer using the body reference from addBody() instead.
Store the api from addBody() and use api.body.sync(alpha) or api.body.view.
Parameters
indexnumberReturns
Object | null — Body buffer view or null if not foundbindInput#
bindInput(inputController : InputController, options : Object)Bind an InputController to automatically sync input to physics. Call this once after init() and after adding the player body.
This handles all internal view registration, so you don't need to manually call setPhysic() or access views directly.
Parameters
inputControllerInputControlleroptionsoptionalObjectDefault is
{}.playerIndexoptionalnumberWhich dynamic body receives input
Default is0.jumpVelocityoptionalnumberJump velocity to apply
Default is8.
updateInput#
updateInput()Sync input state to physics each frame. Call this in your render loop when using bindInput().
addStaticBody#
addStaticBody(mesh : THREE.Object3D, options : Object) : Promise<number>Add a static body and store mesh reference for debug visualization.
Parameters
meshTHREE.Object3DoptionsObjectReturns
Promise<number> — Static body indexaddBody#
addBody(type : BodyType, meshOrCollideBox : THREE.Mesh|THREE.Group|THREE.InstancedMesh|THREE.BatchedMesh, properties : Object, bodyProps : Object, options : Object) : Promise<(Object|Array<Object>)>Add a physics body from a mesh. Convenience method that wraps the addBody helper.
For BatchedMesh/InstancedMesh without instanceMatrixIndex, automatically batches ALL instances
and returns an array of API objects.
Parameters
typeBodyTypemeshOrCollideBoxTHREE.Mesh | THREE.Group | THREE.InstancedMesh | THREE.BatchedMeshpropertiesoptionalObjectDefault is
{}.frictionoptionalnumberSurface friction coefficientmassoptionalnumberBody mass (for dynamic bodies)pFromoptionalnumberPlatform start position (for moving platforms)pTooptionalnumberPlatform end position (for moving platforms)
bodyPropsoptionalObjectDefault is
{}.capsuleRadiusoptionalnumberCapsule collision radius (for dynamic/kinematic)capsuleLengthoptionalnumberCapsule collision length (for dynamic/kinematic)
optionsoptionalObjectDefault is
{}.hitboxoptionalTHREE.MeshCustom hitbox mesh (overrides auto-generated collision)debugMaterialoptionalTHREE.MaterialMaterial for debug visualizationsideoptionalnumberBVH side (e.g., THREE.DoubleSide)instanceMatrixIndexoptionalnumberFor InstancedMesh/BatchedMesh: which instance to use. If omitted, ALL instances are batched automatically.sourceGeometryoptionalTHREE.BufferGeometryFor BatchedMesh single instance: original geometry (skips extraction)sourceGeometriesoptionalObjectFor BatchedMesh batch mode: map of geometryId -> BufferGeometrycustomGeometryToBVHScriptoptionalfunctionCustom geometry processing function
Returns
Promise<(Object|Array<Object>)> — The body API object(s). Returns array for batched BatchedMesh/InstancedMesh (when no instanceMatrixIndex provided).
index{number} - Body index in the physics systemtype{string} - Body type ('static', 'dynamic', 'kinematic', 'zone')entity{THREE.Object3D} - The mesh/entity passed incollideBox{THREE.Mesh} - The collision mesh (may differ from entity)body{Body|DynamicBody|KinematicBody} - Body instance with position/quaternion setters. For kinematic bodies, includes pathfinding methods (enablePathfinding, navigateTo, etc.)instanceIndex{number} - (BatchedMesh/InstancedMesh only) The instance index within the mesh
Syncing to Three.js meshes:
Use api.body.sync(alpha) to synchronize physics state to the mesh with interpolation.
Store the api reference from addBody() and call sync() in your animation loop.
addBodies#
addBodies(type : BodyType, bodiesConfig : Array<Object>) : Promise<Array<Object>>Add multiple physics bodies in a single batch operation. Significantly faster than calling addBody multiple times for many different meshes.
Note: For InstancedMesh/BatchedMesh where all instances share the same properties,
use addBody() without instanceMatrixIndex instead - it auto-batches all instances.
Use addBodies() when you have many different meshes to add.
Parameters
typeBodyTypebodiesConfigArray<Object>meshTHREE.Mesh | THREE.GroupSee nested options below.
meshTHREE.Mesh|THREE.GroupThe mesh to create physics forpropertiesoptionalObjectPhysics propertiesproperties.frictionoptionalnumberSurface friction coefficientproperties.massoptionalnumberBody mass (for dynamic bodies)bodyPropsoptionalObjectBody propertiesbodyProps.capsuleRadiusoptionalnumberCapsule collision radiusbodyProps.capsuleLengthoptionalnumberCapsule collision lengthoptionsoptionalObjectOptional settingsoptions.hitboxoptionalTHREE.MeshCustom hitbox meshoptions.sideoptionalnumberBVH side (e.g., THREE.DoubleSide)options.customGeometryToBVHScriptoptionalfunctionCustom geometry processing
Returns
Promise<Array<Object>> — Array of body API objects, each containing:
index{number} - Body index in the physics systemtype{string} - Body type ('static', 'dynamic', 'kinematic', 'zone')entity{THREE.Object3D} - The mesh/entity passed incollideBox{THREE.Mesh} - The collision meshbody{Body|DynamicBody|KinematicBody} - Body instance with position/quaternion setters
removeBody#
removeBody(bodyOrApiOrIndex : Body|DynamicBody|KinematicBody|Object|number, type : string) : Promise<string>Remove a physics body from the simulation.
Accepts either a Body instance, an API object from addBody(), or type/index directly. Cleans up all main-thread references and notifies the worker to free the body slot.
Parameters
Body | DynamicBody | KinematicBody | Object | numberThe body to remove:
- Body instance (has
typeandindexgetters) - API object from addBody() (has
type,index,bodyproperties) - Body index (requires
typeparameter)
typeoptionalstringReturns
Promise<string> — Confirmation message from workerremoveBodies#
removeBodies(bodiesOrApisOrIndices : Array<(Body|Object|number)>, type : string) : Promise<Array<string>>Remove multiple physics bodies in a single batch operation.
More efficient than calling removeBody multiple times when removing many bodies. All bodies must be of the same type.
Parameters
bodiesOrApisOrIndicesArray<(Body|Object|number)>Array of bodies to remove:
- Body instances
- API objects from addBody()
- Body indices (requires
typeparameter)
typeoptionalstringReturns
Promise<Array<string>> — Array of confirmation messages from workercreateMeshPool#
createMeshPool(templateMesh : THREE.Mesh, poolSize : number, scene : THREE.Scene) : THREE.InstancedMeshCreate an InstancedMesh pool for projectile weapons with automatic mesh syncing.
Returns a THREE.InstancedMesh that automatically syncs with active projectile positions
via Three.js onBeforeRender callback. Mesh instances are positioned, oriented, and
interpolated smoothly between physics updates.
IMPORTANT: Only THREE.Mesh is supported. THREE.Group is NOT supported because
InstancedMesh requires a single geometry and material. For complex projectile visuals,
merge geometries into a single mesh or use the primary visual component.
Parameters
templateMeshTHREE.MeshpoolSizeoptionalnumberDefault is
10.sceneoptionalTHREE.SceneReturns
THREE.InstancedMesh — InstancedMesh with automatic syncing enabledaddWeapon#
addWeapon(type : string, mesh : THREE.Mesh|THREE.InstancedMesh|null, properties : Object) : Promise<Object>Add a weapon instance to the physics system.
For projectile weapons with InstancedMesh (created via createMeshPool()), mesh instances
automatically sync with active projectiles via Three.js onBeforeRender callbacks. No manual
syncing required in your render loop.
Parameters
typestringmeshTHREE.Mesh | THREE.InstancedMesh | nullDefault is
null.propertiesObjectReturns
Promise<Object> — Weapon API object with { index, type, weapon, mesh }removeWeapon#
removeWeapon(weaponIndexOrApi : number|Object)Remove a weapon from the physics system.
Parameters
weaponIndexOrApinumber | ObjectgetStaticMesh#
getStaticMesh(index : number) : THREE.Object3D|nullGet the mesh associated with a static body index.
Parameters
indexnumberReturns
THREE.Object3D | null — The mesh or nulldispose#
dispose()Dispose of the physics engine and free resources.
setFrustumConfig#
setFrustumConfig(config : Object) : Promise<string>Configure the physics frustum culling system. Frustum culling optimizes collision detection by only testing bodies visible in the camera frustum. Near/far planes are inherited from the active camera automatically.
Parameters
configObjectmarginoptionalnumberPadding in world units around the frustum.
Default is10.enabledoptionalbooleanEnable/disable frustum culling.
Default istrue.
Returns
Promise<string> — Confirmation message from worker.setSpatialGridConfig#
setSpatialGridConfig(config : Object) : Promise<string>Configure the physics spatial grid optimization. Spatial grid provides finer-grained spatial filtering after frustum culling, reducing the number of bodies tested for collision based on proximity.
Parameters
configObjectcellSizeoptionalnumberSize of each grid cell in world units.
Default is4.enabledoptionalbooleanEnable/disable spatial grid.
Default istrue.
Returns
Promise<string> — Confirmation message from worker.Static Methods
isSupported#
isSupported() : booleanCheck if SharedArrayBuffer is supported.
Returns
boolean — True if supported