playcanvas
Version:
PlayCanvas WebGL game engine
508 lines (507 loc) • 19.3 kB
TypeScript
/**
* Represents a single point of contact between two colliding rigid bodies in the physics
* simulation. Each contact point stores detailed spatial information about the collision,
* including both local and world space coordinates of the exact contact points on both entities,
* the contact normal direction, and the collision impulse force.
*
* Contact points are generated by the physics engine during collision detection and are typically
* accessed through a {@link ContactResult} object, which can contain multiple contact points for a
* single collision between two entities. Multiple contact points commonly occur when objects
* collide along edges or faces rather than at a single point.
*
* The impulse property can be particularly useful for gameplay mechanics that need to respond
* differently based on the force of impact, such as damage calculations or sound effect volume.
*
* @example
* // Access contact points from a collision event
* entity.collision.on('contact', (result) => {
* // Get the first contact point
* const contact = result.contacts[0];
*
* // Get the contact position in world space
* const worldPos = contact.point;
*
* // Check how hard the collision was
* if (contact.impulse > 10) {
* console.log("That was a hard impact!");
* }
* });
*
* @category Physics
*/
export class ContactPoint {
/**
* Create a new ContactPoint instance.
*
* @param {Vec3} [localPoint] - The point on the entity where the contact occurred, relative to
* the entity.
* @param {Vec3} [localPointOther] - The point on the other entity where the contact occurred,
* relative to the other entity.
* @param {Vec3} [point] - The point on the entity where the contact occurred, in world space.
* @param {Vec3} [pointOther] - The point on the other entity where the contact occurred, in
* world space.
* @param {Vec3} [normal] - The normal vector of the contact on the other entity, in world
* space.
* @param {number} [impulse] - The total accumulated impulse applied by the constraint solver
* during the last sub-step. Describes how hard two objects collide. Defaults to 0.
* @ignore
*/
constructor(localPoint?: Vec3, localPointOther?: Vec3, point?: Vec3, pointOther?: Vec3, normal?: Vec3, impulse?: number);
/**
* The point on the entity where the contact occurred, relative to the entity.
*
* @type {Vec3}
*/
localPoint: Vec3;
/**
* The point on the other entity where the contact occurred, relative to the other entity.
*
* @type {Vec3}
*/
localPointOther: Vec3;
/**
* The point on the entity where the contact occurred, in world space.
*
* @type {Vec3}
*/
point: Vec3;
/**
* The point on the other entity where the contact occurred, in world space.
*
* @type {Vec3}
*/
pointOther: Vec3;
/**
* The normal vector of the contact on the other entity, in world space. This vector points
* away from the surface of the other entity at the point of contact.
*
* @type {Vec3}
*/
normal: Vec3;
/**
* The total accumulated impulse applied by the constraint solver during the last sub-step.
* This value represents how hard two objects collided. Higher values indicate stronger impacts.
*
* @type {number}
*/
impulse: number;
}
/**
* Represents a collection of contact points between two entities in a physics collision.
* When rigid bodies collide, this object stores the entity involved in the collision and
* an array of specific contact points where the collision occurred. This information is
* used by the physics system to resolve collisions and notify components through events.
*
* Instances of this class are passed to event handlers for the `contact` and `collisionstart`
* events on individual {@link RigidBodyComponent} and {@link CollisionComponent} instances.
*
* Unlike {@link SingleContactResult} which is used for global contact events, ContactResult
* objects provide information about collision from the perspective of one entity, with
* information about which other entity was involved and all points of contact.
*
* Please refer to the following event documentation for more information:
*
* - {@link CollisionComponent.EVENT_CONTACT}
* - {@link CollisionComponent.EVENT_COLLISIONSTART}
* - {@link RigidBodyComponent.EVENT_CONTACT}
* - {@link RigidBodyComponent.EVENT_COLLISIONSTART}
*
* @category Physics
*/
export class ContactResult {
/**
* Create a new ContactResult instance.
*
* @param {Entity} other - The entity that was involved in the contact with this entity.
* @param {ContactPoint[]} contacts - An array of ContactPoints with the other entity.
* @ignore
*/
constructor(other: Entity, contacts: ContactPoint[]);
/**
* The entity that was involved in the contact with this entity.
*
* @type {Entity}
*/
other: Entity;
/**
* An array of ContactPoints with the other entity.
*
* @type {ContactPoint[]}
*/
contacts: ContactPoint[];
}
/**
* Contains the result of a successful raycast intersection with a rigid body. When a ray
* intersects with a rigid body in the physics simulation, this class stores the complete
* information about that intersection including the entity, the exact point of impact, the normal
* at the impact point, and the fractional distance along the ray where the intersection occurred.
*
* Instances of this class are created and returned by {@link RigidBodyComponentSystem#raycastFirst}
* and {@link RigidBodyComponentSystem#raycastAll} methods when performing physics raycasts.
*
* @category Physics
*/
export class RaycastResult {
/**
* Create a new RaycastResult instance.
*
* @param {Entity} entity - The entity that was hit.
* @param {Vec3} point - The point at which the ray hit the entity in world space.
* @param {Vec3} normal - The normal vector of the surface where the ray hit in world space.
* @param {number} hitFraction - The normalized distance (between 0 and 1) at which the ray hit
* occurred from the starting point.
* @ignore
*/
constructor(entity: Entity, point: Vec3, normal: Vec3, hitFraction: number);
/**
* The entity that was hit.
*
* @type {Entity}
*/
entity: Entity;
/**
* The point at which the ray hit the entity in world space.
*
* @type {Vec3}
*/
point: Vec3;
/**
* The normal vector of the surface where the ray hit in world space.
*
* @type {Vec3}
*/
normal: Vec3;
/**
* The normalized distance (between 0 and 1) at which the ray hit occurred from the
* starting point.
*
* @type {number}
*/
hitFraction: number;
}
/**
* The RigidBodyComponentSystem manages the physics simulation for all rigid body components
* in the application. It creates and maintains the underlying Ammo.js physics world, handles
* physics object creation and destruction, performs physics raycasting, detects and reports
* collisions, and updates the transforms of entities with rigid bodies after each physics step.
*
* The system controls global physics settings like gravity and provides methods for raycasting
* and collision detection.
*
* This system is only functional if your application has loaded the Ammo.js {@link WasmModule}.
*
* @category Physics
*/
export class RigidBodyComponentSystem extends ComponentSystem {
/**
* Fired when a contact occurs between two rigid bodies. The handler is passed a
* {@link SingleContactResult} object containing details of the contact between the two bodies.
*
* @event
* @example
* app.systems.rigidbody.on('contact', (result) => {
* console.log(`Contact between ${result.a.name} and ${result.b.name}`);
* });
*/
static EVENT_CONTACT: string;
/**
* @type {number}
* @ignore
*/
maxSubSteps: number;
/**
* @type {number}
* @ignore
*/
fixedTimeStep: number;
/**
* The world space vector representing global gravity in the physics simulation. Defaults to
* [0, -9.81, 0] which is an approximation of the gravitational force on Earth.
*
* @type {Vec3}
* @example
* // Set the gravity in the physics world to simulate a planet with low gravity
* app.systems.rigidbody.gravity = new pc.Vec3(0, -3.7, 0);
*/
gravity: Vec3;
/**
* @type {Float32Array}
* @private
*/
private _gravityFloat32;
/**
* @type {RigidBodyComponent[]}
* @private
*/
private _dynamic;
/**
* @type {RigidBodyComponent[]}
* @private
*/
private _kinematic;
/**
* @type {Trigger[]}
* @private
*/
private _triggers;
/**
* @type {CollisionComponent[]}
* @private
*/
private _compounds;
id: string;
_stats: {
fps: number;
ms: number;
dt: number;
updateStart: number;
updateTime: number;
renderStart: number;
renderTime: number;
physicsStart: number;
physicsTime: number;
cullTime: number;
sortTime: number;
skinTime: number;
morphTime: number;
instancingTime: number;
triangles: number;
gsplats: number;
otherPrimitives: number;
shaders: number;
materials: number;
cameras: number;
shadowMapUpdates: number;
shadowMapTime: number;
depthMapTime: number;
forwardTime: number;
lightClustersTime: number;
lightClusters: number;
_timeToCountFrames: number;
_fpsAccum: number;
};
ComponentType: typeof RigidBodyComponent;
DataType: typeof RigidBodyComponentData;
contactPointPool: ObjectPool<typeof ContactPoint>;
contactResultPool: ObjectPool<typeof ContactResult>;
singleContactResultPool: ObjectPool<typeof SingleContactResult>;
schema: string[];
collisions: {};
frameCollisions: {};
/**
* Called once Ammo has been loaded. Responsible for creating the physics world.
*
* @ignore
*/
onLibraryLoaded(): void;
collisionConfiguration: any;
dispatcher: any;
overlappingPairCache: any;
solver: any;
dynamicsWorld: any;
initializeComponentData(component: any, data: any, properties: any): void;
cloneComponent(entity: any, clone: any): Component;
onBeforeRemove(entity: any, component: any): void;
addBody(body: any, group: any, mask: any): void;
removeBody(body: any): void;
createBody(mass: any, shape: any, transform: any): any;
destroyBody(body: any): void;
/**
* Raycast the world and return the first entity the ray hits. Fire a ray into the world from
* start to end, if the ray hits an entity with a collision component, it returns a
* {@link RaycastResult}, otherwise returns null.
*
* @param {Vec3} start - The world space point where the ray starts.
* @param {Vec3} end - The world space point where the ray ends.
* @param {object} [options] - The additional options for the raycasting.
* @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.
* @param {Function} [options.filterCallback] - Custom function to use to filter entities.
* Must return true to proceed with result. Takes one argument: the entity to evaluate.
*
* @returns {RaycastResult|null} The result of the raycasting or null if there was no hit.
*/
raycastFirst(start: Vec3, end: Vec3, options?: {
filterCollisionGroup?: number;
filterCollisionMask?: number;
filterTags?: any[];
filterCallback?: Function;
}): RaycastResult | null;
/**
* Raycast the world and return all entities the ray hits. It returns an array of
* {@link RaycastResult}, one for each hit. If no hits are detected, the returned array will be
* of length 0. Results are sorted by distance with closest first.
*
* @param {Vec3} start - The world space point where the ray starts.
* @param {Vec3} end - The world space point where the ray ends.
* @param {object} [options] - The additional options for the raycasting.
* @param {boolean} [options.sort] - Whether to sort raycast results based on distance with closest
* first. Defaults to false.
* @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.
* @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[]} An array of raycast hit results (0 length if there were no hits).
*
* @example
* // Return all results of a raycast between 0, 2, 2 and 0, -2, -2
* const hits = this.app.systems.rigidbody.raycastAll(new Vec3(0, 2, 2), new Vec3(0, -2, -2));
* @example
* // Return all results of a raycast between 0, 2, 2 and 0, -2, -2
* // where hit entity is tagged with `bird` OR `mammal`
* const hits = this.app.systems.rigidbody.raycastAll(new Vec3(0, 2, 2), new Vec3(0, -2, -2), {
* filterTags: [ "bird", "mammal" ]
* });
* @example
* // Return all results of a raycast between 0, 2, 2 and 0, -2, -2
* // where hit entity has a `camera` component
* const hits = this.app.systems.rigidbody.raycastAll(new Vec3(0, 2, 2), new Vec3(0, -2, -2), {
* filterCallback: (entity) => entity && entity.camera
* });
* @example
* // Return all results of a raycast between 0, 2, 2 and 0, -2, -2
* // where hit entity is tagged with (`carnivore` AND `mammal`) OR (`carnivore` AND `reptile`)
* // and the entity has an `anim` component
* const hits = this.app.systems.rigidbody.raycastAll(new Vec3(0, 2, 2), new Vec3(0, -2, -2), {
* filterTags: [
* [ "carnivore", "mammal" ],
* [ "carnivore", "reptile" ]
* ],
* filterCallback: (entity) => entity && entity.anim
* });
*/
raycastAll(start: Vec3, end: Vec3, options?: {
sort?: boolean;
filterCollisionGroup?: number;
filterCollisionMask?: number;
filterTags?: any[];
filterCallback?: Function;
}): RaycastResult[];
/**
* Stores a collision between the entity and other in the contacts map and returns true if it
* is a new collision.
*
* @param {Entity} entity - The entity.
* @param {Entity} other - The entity that collides with the first entity.
* @returns {boolean} True if this is a new collision, false otherwise.
* @private
*/
private _storeCollision;
_createContactPointFromAmmo(contactPoint: any): ContactPoint;
_createReverseContactPointFromAmmo(contactPoint: any): ContactPoint;
_createSingleContactResult(a: any, b: any, contactPoint: any): SingleContactResult;
_createContactResult(other: any, contacts: any): ContactResult;
/**
* Removes collisions that no longer exist from the collisions list and fires collisionend
* events to the related entities.
*
* @private
*/
private _cleanOldCollisions;
/**
* Returns true if the entity has a contact event attached and false otherwise.
*
* @param {Entity} entity - Entity to test.
* @returns {boolean} True if the entity has a contact and false otherwise.
* @private
*/
private _hasContactEvent;
/**
* Checks for collisions and fires collision events.
*
* @param {number} world - The pointer to the dynamics world that invoked this callback.
* @param {number} timeStep - The amount of simulation time processed in the last simulation tick.
* @private
*/
private _checkForCollisions;
onUpdate(dt: any): void;
}
/**
* Represents the detailed data of a single contact point between two rigid bodies in the physics
* simulation. This class provides comprehensive information about the contact, including the
* entities involved, the exact contact points in both local and world space coordinates, the
* contact normal, and the collision impulse force.
*
* Instances of this class are created by the physics engine when collision events occur and are
* passed to event handlers only through the global `contact` event on the
* {@link RigidBodyComponentSystem}. Individual rigid body components receive instances of
* {@link ContactResult} instead.
*
* @example
* app.systems.rigidbody.on('contact', (result) => {
* console.log(`Contact between ${result.a.name} and ${result.b.name}`);
* });
* @category Physics
*/
export class SingleContactResult {
/**
* Create a new SingleContactResult instance.
*
* @param {Entity} a - The first entity involved in the contact.
* @param {Entity} b - The second entity involved in the contact.
* @param {ContactPoint} contactPoint - The contact point between the two entities.
* @ignore
*/
constructor(a: Entity, b: Entity, contactPoint: ContactPoint, ...args: any[]);
/**
* The first entity involved in the contact.
*
* @type {Entity}
*/
a: Entity;
/**
* The second entity involved in the contact.
*
* @type {Entity}
*/
b: Entity;
/**
* The total accumulated impulse applied by the constraint solver during the last
* sub-step. Describes how hard two bodies collided.
*
* @type {number}
*/
impulse: number;
/**
* The point on Entity A where the contact occurred, relative to A.
*
* @type {Vec3}
*/
localPointA: Vec3;
/**
* The point on Entity B where the contact occurred, relative to B.
*
* @type {Vec3}
*/
localPointB: Vec3;
/**
* The point on Entity A where the contact occurred, in world space.
*
* @type {Vec3}
*/
pointA: Vec3;
/**
* The point on Entity B where the contact occurred, in world space.
*
* @type {Vec3}
*/
pointB: Vec3;
/**
* The normal vector of the contact on Entity B, in world space.
*
* @type {Vec3}
*/
normal: Vec3;
}
import { Vec3 } from '../../../core/math/vec3.js';
import type { Entity } from '../../entity.js';
import { ComponentSystem } from '../system.js';
import { RigidBodyComponent } from './component.js';
import { RigidBodyComponentData } from './data.js';
import { ObjectPool } from '../../../core/object-pool.js';
import { Component } from '../component.js';