UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

508 lines (507 loc) 19.3 kB
/** * 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';