playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
645 lines (644 loc) • 25.1 kB
TypeScript
export type PhysicsBodyDesc = {
/**
* - The body type: BODYTYPE_STATIC, BODYTYPE_DYNAMIC or
* BODYTYPE_KINEMATIC.
*/
type: string;
/**
* - The construction mass. Callers pass 0 for non-dynamic bodies.
*/
mass: number;
/**
* - An opaque collision shape handle created by
* {@link PhysicsWorld#createShape}.
*/
shape: object;
/**
* - The initial world space position. Collision component offsets are
* already applied by the caller.
*/
position: Vec3;
/**
* - The initial world space rotation.
*/
rotation: Quat;
/**
* - The entity this body simulates. Surfaced by raycast and
* contact results.
*/
entity: Entity | null;
/**
* - When true, the body collides and reports contacts
* but does not respond to them (used for triggers). Defaults to false.
*/
noContactResponse?: boolean;
};
export type PhysicsMeshSource = {
/**
* - A stable cache key for the source geometry (mesh id). Backends may
* cache built triangle data per id for the lifetime of the world.
*/
id: number;
/**
* - Vertex positions, possibly interleaved.
*/
positions: Float32Array | number[];
/**
* - The number of floats between consecutive positions (3 when
* tightly packed).
*/
stride: number;
/**
* - Triangle indices. Ignored when
* convexHull is true.
*/
indices: number[] | Uint16Array | Uint32Array;
/**
* - The first index to read.
*/
base: number;
/**
* - The number of indices to read (a multiple of 3). Ignored when
* convexHull is true - hulls consume every position.
*/
count: number;
/**
* - Build a convex hull instead of a triangle mesh.
*/
convexHull: boolean;
/**
* - Weld duplicate vertices while building triangle data.
*/
checkDuplicates: boolean;
/**
* - Scaling to bake into the positions while building, or null.
*/
bakeScale: Vec3 | null;
/**
* - Scaling to apply to the built sub-shape, or null.
*/
shapeScale: Vec3 | null;
/**
* - The sub-shape position within the mesh shape. Collision component
* offsets are already applied by the caller.
*/
position: Vec3;
/**
* - The sub-shape rotation within the mesh shape.
*/
rotation: Quat;
};
export type PhysicsShapeDesc = {
/**
* - The shape type: 'box', 'sphere', 'capsule', 'cylinder', 'cone',
* 'mesh' or 'compound'.
*/
type: string;
/**
* - The box half extents.
*/
halfExtents?: Vec3;
/**
* - The sphere/capsule/cylinder/cone radius.
*/
radius?: number;
/**
* - The full capsule/cylinder/cone height along the alignment axis.
* Backends convert to their own conventions.
*/
height?: number;
/**
* - The capsule/cylinder/cone alignment axis: 0 (X), 1 (Y) or 2 (Z).
*/
axis?: number;
/**
* - The geometry sources of a 'mesh' shape. Mesh
* shapes are created in one atomic call so backends may build immutable composites.
*/
sources?: PhysicsMeshSource[];
/**
* - Whole-shape scaling of a 'mesh' shape, or null.
*/
scale?: Vec3 | null;
};
/**
* The parameter bag consumed by {@link PhysicsWorld#createJoint} and the PhysicsJoint update
* methods. JointComponent structurally satisfies this contract and passes itself.
*/
export type PhysicsJointSettings = {
/**
* - Whether hinge/slider limits are enabled.
*/
enableLimits: boolean;
/**
* - The hinge (degrees) or slider (meters) limits.
*/
limits: Vec2;
/**
* - The hinge (deg/s) or slider (m/s) motor speed.
*/
motorSpeed: number;
/**
* - The maximum motor force. The motor is engaged while > 0.
*/
maxMotorForce: number;
/**
* - The ball joint swing limit around Y in degrees.
*/
swingLimitY: number;
/**
* - The ball joint swing limit around Z in degrees.
*/
swingLimitZ: number;
/**
* - The ball joint twist limit in degrees.
*/
twistLimit: number;
/**
* - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
*/
linearMotionX: string;
/**
* - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
*/
linearMotionY: string;
/**
* - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
*/
linearMotionZ: string;
/**
* - The 6dof linear limits along X in meters.
*/
linearLimitsX: Vec2;
/**
* - The 6dof linear limits along Y in meters.
*/
linearLimitsY: Vec2;
/**
* - The 6dof linear limits along Z in meters.
*/
linearLimitsZ: Vec2;
/**
* - The 6dof linear spring stiffness per axis.
*/
linearStiffness: Vec3;
/**
* - The 6dof linear spring damping per axis.
*/
linearDamping: Vec3;
/**
* - The 6dof linear spring equilibrium point per axis.
*/
linearEquilibrium: Vec3;
/**
* - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
*/
angularMotionX: string;
/**
* - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
*/
angularMotionY: string;
/**
* - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
*/
angularMotionZ: string;
/**
* - The 6dof angular limits around X in degrees.
*/
angularLimitsX: Vec2;
/**
* - The 6dof angular limits around Y in degrees.
*/
angularLimitsY: Vec2;
/**
* - The 6dof angular limits around Z in degrees.
*/
angularLimitsZ: Vec2;
/**
* - The 6dof angular spring stiffness per axis.
*/
angularStiffness: Vec3;
/**
* - The 6dof angular spring damping per axis.
*/
angularDamping: Vec3;
/**
* - The 6dof angular spring equilibrium per axis in degrees.
*/
angularEquilibrium: Vec3;
};
export type PhysicsJointDesc = {
/**
* - The joint type: JOINTTYPE_FIXED, JOINTTYPE_BALL, JOINTTYPE_HINGE,
* JOINTTYPE_SLIDER or JOINTTYPE_6DOF.
*/
type: string;
/**
* - The first constrained body.
*/
bodyA: PhysicsBody;
/**
* - The second constrained body, or null to anchor the joint
* to the world (the backend supplies its own fixed anchor body).
*/
bodyB: PhysicsBody | null;
/**
* - The joint frame in body A's local space, as a scale-free matrix
* with X as the primary joint axis by engine convention. Backends apply their own native axis
* corrections. Only valid during the call.
*/
frameA: Mat4;
/**
* - The joint frame in body B's local space (or world space when bodyB
* is null).
*/
frameB: Mat4;
/**
* - Whether the two bodies keep colliding with each other.
* Creation-time only - changing it recreates the joint.
*/
enableCollision: boolean;
/**
* - The full parameter bag, applied at creation.
*/
settings: PhysicsJointSettings;
};
/**
* A single contacting body pair reported to the {@link PhysicsContactListener}. Backends reuse
* one instance per world - it is only valid inside onContactPair and must never be retained.
*/
export type PhysicsContactPair = {
/**
* - The entity owning the first body.
*/
entityA: Entity;
/**
* - The entity owning the second body.
*/
entityB: Entity;
/**
* - True if the first body has no contact response.
*/
triggerA: boolean;
/**
* - True if the second body has no contact response.
*/
triggerB: boolean;
/**
* - The number of contact points (always >= 1).
*/
contactCount: number;
/**
* - Fills out with contact
* point index from A's perspective, allocation free.
*/
readContact: (index: number, out: ContactPoint) => void;
};
/**
* Receives contact reports from a {@link PhysicsWorld}. The world runs one complete contact
* pass - onContactsBegin, zero or more onContactPair, onContactsEnd - once per simulation
* substep where the backend supports substep granularity, otherwise at least once per step.
*/
export type PhysicsContactListener = {
/**
* - Called when a contact pass begins.
*/
onContactsBegin: () => void;
/**
* - Called for each contacting
* pair. Pairs with no contact points or without entities on both bodies are not reported.
*/
onContactPair: (pair: PhysicsContactPair) => void;
/**
* - Called when a contact pass ends.
*/
onContactsEnd: () => void;
};
/**
* @import { Entity } from '../entity.js'
* @import { Mat4 } from '../../core/math/mat4.js'
* @import { Quat } from '../../core/math/quat.js'
* @import { Vec2 } from '../../core/math/vec2.js'
* @import { Vec3 } from '../../core/math/vec3.js'
* @import { ContactPoint } from '../components/rigid-body/contact-point.js'
* @import { RaycastResult } from '../components/rigid-body/raycast-result.js'
*/
/**
* @typedef {object} PhysicsBodyDesc
* @property {string} type - The body type: BODYTYPE_STATIC, BODYTYPE_DYNAMIC or
* BODYTYPE_KINEMATIC.
* @property {number} mass - The construction mass. Callers pass 0 for non-dynamic bodies.
* @property {object} shape - An opaque collision shape handle created by
* {@link PhysicsWorld#createShape}.
* @property {Vec3} position - The initial world space position. Collision component offsets are
* already applied by the caller.
* @property {Quat} rotation - The initial world space rotation.
* @property {Entity|null} entity - The entity this body simulates. Surfaced by raycast and
* contact results.
* @property {boolean} [noContactResponse] - When true, the body collides and reports contacts
* but does not respond to them (used for triggers). Defaults to false.
* @ignore
*/
/**
* @typedef {object} PhysicsMeshSource
* @property {number} id - A stable cache key for the source geometry (mesh id). Backends may
* cache built triangle data per id for the lifetime of the world.
* @property {Float32Array|number[]} positions - Vertex positions, possibly interleaved.
* @property {number} stride - The number of floats between consecutive positions (3 when
* tightly packed).
* @property {number[]|Uint16Array|Uint32Array} indices - Triangle indices. Ignored when
* convexHull is true.
* @property {number} base - The first index to read.
* @property {number} count - The number of indices to read (a multiple of 3). Ignored when
* convexHull is true - hulls consume every position.
* @property {boolean} convexHull - Build a convex hull instead of a triangle mesh.
* @property {boolean} checkDuplicates - Weld duplicate vertices while building triangle data.
* @property {Vec3|null} bakeScale - Scaling to bake into the positions while building, or null.
* @property {Vec3|null} shapeScale - Scaling to apply to the built sub-shape, or null.
* @property {Vec3} position - The sub-shape position within the mesh shape. Collision component
* offsets are already applied by the caller.
* @property {Quat} rotation - The sub-shape rotation within the mesh shape.
* @ignore
*/
/**
* @typedef {object} PhysicsShapeDesc
* @property {string} type - The shape type: 'box', 'sphere', 'capsule', 'cylinder', 'cone',
* 'mesh' or 'compound'.
* @property {Vec3} [halfExtents] - The box half extents.
* @property {number} [radius] - The sphere/capsule/cylinder/cone radius.
* @property {number} [height] - The full capsule/cylinder/cone height along the alignment axis.
* Backends convert to their own conventions.
* @property {number} [axis] - The capsule/cylinder/cone alignment axis: 0 (X), 1 (Y) or 2 (Z).
* @property {PhysicsMeshSource[]} [sources] - The geometry sources of a 'mesh' shape. Mesh
* shapes are created in one atomic call so backends may build immutable composites.
* @property {Vec3|null} [scale] - Whole-shape scaling of a 'mesh' shape, or null.
* @ignore
*/
/**
* The parameter bag consumed by {@link PhysicsWorld#createJoint} and the PhysicsJoint update
* methods. JointComponent structurally satisfies this contract and passes itself.
*
* @typedef {object} PhysicsJointSettings
* @property {boolean} enableLimits - Whether hinge/slider limits are enabled.
* @property {Vec2} limits - The hinge (degrees) or slider (meters) limits.
* @property {number} motorSpeed - The hinge (deg/s) or slider (m/s) motor speed.
* @property {number} maxMotorForce - The maximum motor force. The motor is engaged while > 0.
* @property {number} swingLimitY - The ball joint swing limit around Y in degrees.
* @property {number} swingLimitZ - The ball joint swing limit around Z in degrees.
* @property {number} twistLimit - The ball joint twist limit in degrees.
* @property {string} linearMotionX - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
* @property {string} linearMotionY - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
* @property {string} linearMotionZ - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
* @property {Vec2} linearLimitsX - The 6dof linear limits along X in meters.
* @property {Vec2} linearLimitsY - The 6dof linear limits along Y in meters.
* @property {Vec2} linearLimitsZ - The 6dof linear limits along Z in meters.
* @property {Vec3} linearStiffness - The 6dof linear spring stiffness per axis.
* @property {Vec3} linearDamping - The 6dof linear spring damping per axis.
* @property {Vec3} linearEquilibrium - The 6dof linear spring equilibrium point per axis.
* @property {string} angularMotionX - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
* @property {string} angularMotionY - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
* @property {string} angularMotionZ - MOTION_FREE, MOTION_LIMITED or MOTION_LOCKED.
* @property {Vec2} angularLimitsX - The 6dof angular limits around X in degrees.
* @property {Vec2} angularLimitsY - The 6dof angular limits around Y in degrees.
* @property {Vec2} angularLimitsZ - The 6dof angular limits around Z in degrees.
* @property {Vec3} angularStiffness - The 6dof angular spring stiffness per axis.
* @property {Vec3} angularDamping - The 6dof angular spring damping per axis.
* @property {Vec3} angularEquilibrium - The 6dof angular spring equilibrium per axis in degrees.
* @ignore
*/
/**
* @typedef {object} PhysicsJointDesc
* @property {string} type - The joint type: JOINTTYPE_FIXED, JOINTTYPE_BALL, JOINTTYPE_HINGE,
* JOINTTYPE_SLIDER or JOINTTYPE_6DOF.
* @property {PhysicsBody} bodyA - The first constrained body.
* @property {PhysicsBody|null} bodyB - The second constrained body, or null to anchor the joint
* to the world (the backend supplies its own fixed anchor body).
* @property {Mat4} frameA - The joint frame in body A's local space, as a scale-free matrix
* with X as the primary joint axis by engine convention. Backends apply their own native axis
* corrections. Only valid during the call.
* @property {Mat4} frameB - The joint frame in body B's local space (or world space when bodyB
* is null).
* @property {boolean} enableCollision - Whether the two bodies keep colliding with each other.
* Creation-time only - changing it recreates the joint.
* @property {PhysicsJointSettings} settings - The full parameter bag, applied at creation.
* @ignore
*/
/**
* A single contacting body pair reported to the {@link PhysicsContactListener}. Backends reuse
* one instance per world - it is only valid inside onContactPair and must never be retained.
*
* @typedef {object} PhysicsContactPair
* @property {Entity} entityA - The entity owning the first body.
* @property {Entity} entityB - The entity owning the second body.
* @property {boolean} triggerA - True if the first body has no contact response.
* @property {boolean} triggerB - True if the second body has no contact response.
* @property {number} contactCount - The number of contact points (always >= 1).
* @property {(index: number, out: ContactPoint) => void} readContact - Fills out with contact
* point index from A's perspective, allocation free.
* @ignore
*/
/**
* Receives contact reports from a {@link PhysicsWorld}. The world runs one complete contact
* pass - onContactsBegin, zero or more onContactPair, onContactsEnd - once per simulation
* substep where the backend supports substep granularity, otherwise at least once per step.
*
* @typedef {object} PhysicsContactListener
* @property {() => void} onContactsBegin - Called when a contact pass begins.
* @property {(pair: PhysicsContactPair) => void} onContactPair - Called for each contacting
* pair. Pairs with no contact points or without entities on both bodies are not reported.
* @property {() => void} onContactsEnd - Called when a contact pass ends.
* @ignore
*/
/**
* The base class for physics backends. A PhysicsWorld owns the lifecycle of a physics engine's
* simulation world: stepping, gravity, body and shape factories, joints, raycasts and contact
* reporting. Backends subclass it (AmmoPhysicsWorld) and override every method.
*
* The base implementation is a functional no-op: bodies and joints are created but inert,
* raycasts miss and stepping does nothing. NullPhysicsWorld uses this to let component
* lifecycle run in tests without a physics engine loaded.
*
* @ignore
*/
export class PhysicsWorld {
/**
* Create a new PhysicsWorld instance.
*
* @param {object} [options] - The world options.
* @param {PhysicsContactListener} [options.contactListener] - The contact listener.
*/
constructor(options?: {
contactListener?: PhysicsContactListener;
});
/**
* The listener driven during {@link PhysicsWorld#step}. Set once at construction.
*
* @type {PhysicsContactListener|null}
*/
contactListener: PhysicsContactListener | null;
/**
* The backend-native world object - btDiscreteDynamicsWorld when the Ammo backend is
* active, null otherwise.
*
* @type {object|null}
*/
nativeWorld: object | null;
/**
* Destroys the world and all native resources it owns. The world is unusable afterwards.
*/
destroy(): void;
/**
* Applies gravity if it differs from the current world gravity. Safe (and expected) to be
* called every frame - backends deduplicate.
*
* @param {Vec3} gravity - The world space gravity.
*/
setGravity(gravity: Vec3): void;
/**
* Advances the simulation. Contact passes are driven from inside this call on backends
* that support substep granularity.
*
* @param {number} dt - The elapsed time in seconds.
* @param {number} maxSubSteps - The maximum number of fixed substeps to take.
* @param {number} fixedTimeStep - The duration of a fixed substep in seconds.
*/
step(dt: number, maxSubSteps: number, fixedTimeStep: number): void;
/**
* Runs a deferred contact pass on backends that could not report contacts from inside
* {@link PhysicsWorld#step}. Called by the system after dynamic transform sync. No-op on
* backends that report during step.
*/
flushContacts(): void;
/**
* Creates a body outside the simulation. Add it with {@link PhysicsWorld#addBody}.
*
* @param {PhysicsBodyDesc} desc - The body descriptor.
* @returns {PhysicsBody} The new body.
*/
createBody(desc: PhysicsBodyDesc): PhysicsBody;
/**
* Destroys a body. It must not currently be in the simulation.
*
* @param {PhysicsBody} body - The body to destroy.
*/
destroyBody(body: PhysicsBody): void;
/**
* Adds a body to the simulation and applies the backend's activation policy for the body's
* type (kinematic bodies never deactivate, all others enter the active state).
*
* @param {PhysicsBody} body - The body to add.
* @param {number} [group] - The collision group bits. Used together with mask.
* @param {number} [mask] - The collision mask bits.
*/
addBody(body: PhysicsBody, group?: number, mask?: number): void;
/**
* Removes a body from the simulation. The body becomes inert until re-added.
*
* @param {PhysicsBody} body - The body to remove.
*/
removeBody(body: PhysicsBody): void;
/**
* Creates a collision shape from a typed descriptor. The returned handle is opaque - it is
* owned by this world and must only be passed back to this world.
*
* @param {PhysicsShapeDesc} desc - The shape descriptor.
* @returns {object} The opaque shape handle.
*/
createShape(desc: PhysicsShapeDesc): object;
/**
* Destroys a shape handle created by {@link PhysicsWorld#createShape}.
*
* @param {object} shape - The shape handle.
*/
destroyShape(shape: object): void;
/**
* Adds a child shape to a 'compound' shape at a local pose.
*
* @param {object} compound - The compound shape handle.
* @param {object} child - The child shape handle.
* @param {Vec3} position - The child position in the compound's local space.
* @param {Quat} rotation - The child rotation in the compound's local space.
*/
addCompoundChild(compound: object, child: object, position: Vec3, rotation: Quat): void;
/**
* Updates the local pose of a child within a 'compound' shape. Adds the child if it is not
* present.
*
* @param {object} compound - The compound shape handle.
* @param {object} child - The child shape handle.
* @param {Vec3} position - The child position in the compound's local space.
* @param {Quat} rotation - The child rotation in the compound's local space.
*/
updateCompoundChild(compound: object, child: object, position: Vec3, rotation: Quat): void;
/**
* Removes a child from a 'compound' shape. No-op if the child is not present.
*
* @param {object} compound - The compound shape handle.
* @param {object} child - The child shape handle.
*/
removeCompoundChild(compound: object, child: object): void;
/**
* Returns the number of children in a 'compound' shape.
*
* @param {object} compound - The compound shape handle.
* @returns {number} The child count.
*/
getCompoundChildCount(compound: object): number;
/**
* Creates a joint between two bodies (or one body and the world) and adds it to the
* simulation.
*
* @param {PhysicsJointDesc} desc - The joint descriptor.
* @returns {PhysicsJoint} The new joint.
*/
createJoint(desc: PhysicsJointDesc): PhysicsJoint;
/**
* Removes a joint from the simulation and destroys it.
*
* @param {PhysicsJoint} joint - The joint to destroy.
*/
destroyJoint(joint: PhysicsJoint): void;
/**
* Raycast the world and return the first hit.
*
* @param {Vec3} start - The world space start point.
* @param {Vec3} end - The world space end point.
* @param {object} [options] - The raycast options.
* @param {number} [options.filterCollisionGroup] - Collision group to apply to the raycast.
* @param {number} [options.filterCollisionMask] - Collision mask to apply to the raycast.
* @returns {RaycastResult|null} The hit, or null if there was none.
*/
raycastFirst(start: Vec3, end: Vec3, options?: {
filterCollisionGroup?: number;
filterCollisionMask?: number;
}): RaycastResult | null;
/**
* Raycast the world and return all hits, in backend order.
*
* @param {Vec3} start - The world space start point.
* @param {Vec3} end - The world space end point.
* @param {object} [options] - The raycast options.
* @param {number} [options.filterCollisionGroup] - Collision group to apply to the raycast.
* @param {number} [options.filterCollisionMask] - Collision mask to apply to the raycast.
* @param {any[]} [options.filterTags] - Tags filters. Defined the same way as a
* {@link Tags#has} query but within an array. Hits filtered here are never allocated.
* @param {Function} [options.filterCallback] - Custom function to use to filter entities.
* Must return true to proceed with result. Takes the entity to evaluate as argument.
* @returns {RaycastResult[]} The hits (0 length if there were none).
*/
raycastAll(start: Vec3, end: Vec3, options?: {
filterCollisionGroup?: number;
filterCollisionMask?: number;
filterTags?: any[];
filterCallback?: Function;
}): RaycastResult[];
}
import type { Vec3 } from '../../core/math/vec3.js';
import type { Quat } from '../../core/math/quat.js';
import type { Entity } from '../entity.js';
import type { Vec2 } from '../../core/math/vec2.js';
import { PhysicsBody } from './physics-body.js';
import type { Mat4 } from '../../core/math/mat4.js';
import type { ContactPoint } from '../components/rigid-body/contact-point.js';
import { PhysicsJoint } from './physics-joint.js';
import type { RaycastResult } from '../components/rigid-body/raycast-result.js';