import { Physics_internal } from "@24tools/playable_template"; import { Box3, Object3D, Vector3 } from "three"; import { Body, Box, Quaternion, Sphere, Vec3 } from "cannon-es"; // Collision layers. Each body has a `group` (what it IS) and a `mask` (what it // COLLIDES WITH); two bodies interact only if each one's group is in the other's // mask. Values are bit flags so masks can be OR-combined (e.g. Wall | Trigger). export enum PhysicsLayer { Player = 1, Wall = 2, Trigger = 4, Enemy = 8, } /** * Wraps a three.js object in a cannon-es rigid body. The shape is derived from * the object: the player gets a Sphere (rolls smoothly along walls/floor), * everything else gets a Box sized to the object's bounding box. */ export class PhysicsBody { private body: Body; constructor( object: Object3D, isTrigger: boolean, mass: number, collisionGroup: PhysicsLayer, collisionMask: PhysicsLayer, sphereRadius = 0.3, ) { const isPlayer = collisionGroup === PhysicsLayer.Player; // Measure the bounding box with rotation temporarily zeroed, so the box // half-extents match the object's un-rotated size; the body's own // quaternion (set below) then applies the real orientation. const savedRotation = object.quaternion.clone(); object.quaternion.copy(new Quaternion()); const size = new Box3().setFromObject(object).getSize(new Vector3()); object.quaternion.copy(savedRotation); this.body = new Body({ isTrigger, mass, shape: isPlayer ? new Sphere(sphereRadius) : new Box(new Vec3(size.x / 2, size.y / 2, size.z / 2)), collisionFilterGroup: collisionGroup, collisionFilterMask: collisionMask, }); const worldPos = object.getWorldPosition(new Vector3()); this.body.position.set(worldPos.x, worldPos.y, worldPos.z); this.body.quaternion.setFromEuler( object.rotation.x, object.rotation.y, object.rotation.z, "XYZ", ); Physics_internal.physicsWorld?.addBody(this.body); } /** The underlying cannon body (for direct velocity/position control). */ getPhysicsBody(): Body { return this.body; } /** Remove the body from the physics world (e.g. when a crate breaks). */ destroy() { Physics_internal.physicsWorld?.removeBody(this.body); } }