Files
esengine/packages/rapier2d/src/pipeline/world.ts

1261 lines
45 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
RawBroadPhase,
RawCCDSolver,
RawColliderSet,
RawDeserializedWorld,
RawIntegrationParameters,
RawIslandManager,
RawImpulseJointSet,
RawMultibodyJointSet,
RawNarrowPhase,
RawPhysicsPipeline,
RawRigidBodySet,
RawSerializationPipeline,
RawDebugRenderPipeline,
} from "../raw";
import {
BroadPhase,
Collider,
ColliderDesc,
ColliderHandle,
ColliderSet,
InteractionGroups,
NarrowPhase,
PointColliderProjection,
Ray,
RayColliderIntersection,
RayColliderHit,
Shape,
ColliderShapeCastHit,
TempContactManifold,
} from "../geometry";
import {
CCDSolver,
IntegrationParameters,
IslandManager,
ImpulseJoint,
ImpulseJointHandle,
MultibodyJoint,
MultibodyJointHandle,
JointData,
ImpulseJointSet,
MultibodyJointSet,
RigidBody,
RigidBodyDesc,
RigidBodyHandle,
RigidBodySet,
} from "../dynamics";
import {Rotation, Vector, VectorOps} from "../math";
import {PhysicsPipeline} from "./physics_pipeline";
import {QueryFilterFlags} from "./query_pipeline";
import {SerializationPipeline} from "./serialization_pipeline";
import {EventQueue} from "./event_queue";
import {PhysicsHooks} from "./physics_hooks";
import {DebugRenderBuffers, DebugRenderPipeline} from "./debug_render_pipeline";
import {
KinematicCharacterController,
PidAxesMask,
PidController,
} from "../control";
import {Coarena} from "../coarena";
/**
* The physics world.
*
* This contains all the data-structures necessary for creating and simulating
* bodies with contacts, joints, and external forces.
*/
export class World {
public gravity: Vector;
integrationParameters: IntegrationParameters;
islands: IslandManager;
broadPhase: BroadPhase;
narrowPhase: NarrowPhase;
bodies: RigidBodySet;
colliders: ColliderSet;
impulseJoints: ImpulseJointSet;
multibodyJoints: MultibodyJointSet;
ccdSolver: CCDSolver;
physicsPipeline: PhysicsPipeline;
serializationPipeline: SerializationPipeline;
debugRenderPipeline: DebugRenderPipeline;
characterControllers: Set<KinematicCharacterController>;
pidControllers: Set<PidController>;
/**
* Release the WASM memory occupied by this physics world.
*
* All the fields of this physics world will be freed as well,
* so there is no need to call their `.free()` methods individually.
*/
public free() {
this.integrationParameters.free();
this.islands.free();
this.broadPhase.free();
this.narrowPhase.free();
this.bodies.free();
this.colliders.free();
this.impulseJoints.free();
this.multibodyJoints.free();
this.ccdSolver.free();
this.physicsPipeline.free();
this.serializationPipeline.free();
this.debugRenderPipeline.free();
this.characterControllers.forEach((controller) => controller.free());
this.pidControllers.forEach((controller) => controller.free());
this.integrationParameters = undefined;
this.islands = undefined;
this.broadPhase = undefined;
this.narrowPhase = undefined;
this.bodies = undefined;
this.colliders = undefined;
this.ccdSolver = undefined;
this.impulseJoints = undefined;
this.multibodyJoints = undefined;
this.physicsPipeline = undefined;
this.serializationPipeline = undefined;
this.debugRenderPipeline = undefined;
this.characterControllers = undefined;
this.pidControllers = undefined;
}
constructor(
gravity: Vector,
rawIntegrationParameters?: RawIntegrationParameters,
rawIslands?: RawIslandManager,
rawBroadPhase?: RawBroadPhase,
rawNarrowPhase?: RawNarrowPhase,
rawBodies?: RawRigidBodySet,
rawColliders?: RawColliderSet,
rawImpulseJoints?: RawImpulseJointSet,
rawMultibodyJoints?: RawMultibodyJointSet,
rawCCDSolver?: RawCCDSolver,
rawPhysicsPipeline?: RawPhysicsPipeline,
rawSerializationPipeline?: RawSerializationPipeline,
rawDebugRenderPipeline?: RawDebugRenderPipeline,
) {
this.gravity = gravity;
this.integrationParameters = new IntegrationParameters(
rawIntegrationParameters,
);
this.islands = new IslandManager(rawIslands);
this.broadPhase = new BroadPhase(rawBroadPhase);
this.narrowPhase = new NarrowPhase(rawNarrowPhase);
this.bodies = new RigidBodySet(rawBodies);
this.colliders = new ColliderSet(rawColliders);
this.impulseJoints = new ImpulseJointSet(rawImpulseJoints);
this.multibodyJoints = new MultibodyJointSet(rawMultibodyJoints);
this.ccdSolver = new CCDSolver(rawCCDSolver);
this.physicsPipeline = new PhysicsPipeline(rawPhysicsPipeline);
this.serializationPipeline = new SerializationPipeline(
rawSerializationPipeline,
);
this.debugRenderPipeline = new DebugRenderPipeline(
rawDebugRenderPipeline,
);
this.characterControllers = new Set<KinematicCharacterController>();
this.pidControllers = new Set<PidController>();
this.impulseJoints.finalizeDeserialization(this.bodies);
this.bodies.finalizeDeserialization(this.colliders);
this.colliders.finalizeDeserialization(this.bodies);
}
public static fromRaw(raw: RawDeserializedWorld): World {
if (!raw) return null;
return new World(
VectorOps.fromRaw(raw.takeGravity()),
raw.takeIntegrationParameters(),
raw.takeIslandManager(),
raw.takeBroadPhase(),
raw.takeNarrowPhase(),
raw.takeBodies(),
raw.takeColliders(),
raw.takeImpulseJoints(),
raw.takeMultibodyJoints(),
);
}
/**
* Takes a snapshot of this world.
*
* Use `World.restoreSnapshot` to create a new physics world with a state identical to
* the state when `.takeSnapshot()` is called.
*/
public takeSnapshot(): Uint8Array {
return this.serializationPipeline.serializeAll(
this.gravity,
this.integrationParameters,
this.islands,
this.broadPhase,
this.narrowPhase,
this.bodies,
this.colliders,
this.impulseJoints,
this.multibodyJoints,
);
}
/**
* Creates a new physics world from a snapshot.
*
* This new physics world will be an identical copy of the snapshoted physics world.
*/
public static restoreSnapshot(data: Uint8Array): World {
let deser = new SerializationPipeline();
return deser.deserializeAll(data);
}
/**
* Computes all the lines (and their colors) needed to render the scene.
*
* @param filterFlags - Flags for excluding whole subsets of colliders from rendering.
* @param filterPredicate - Any collider for which this closure returns `false` will be excluded from the
* debug rendering.
*/
public debugRender(
filterFlags?: QueryFilterFlags,
filterPredicate?: (collider: Collider) => boolean,
): DebugRenderBuffers {
this.debugRenderPipeline.render(
this.bodies,
this.colliders,
this.impulseJoints,
this.multibodyJoints,
this.narrowPhase,
filterFlags,
filterPredicate,
);
return new DebugRenderBuffers(
this.debugRenderPipeline.vertices,
this.debugRenderPipeline.colors,
);
}
/**
* Advance the simulation by one time step.
*
* All events generated by the physics engine are ignored.
*
* @param EventQueue - (optional) structure responsible for collecting
* events generated by the physics engine.
*/
public step(eventQueue?: EventQueue, hooks?: PhysicsHooks) {
this.physicsPipeline.step(
this.gravity,
this.integrationParameters,
this.islands,
this.broadPhase,
this.narrowPhase,
this.bodies,
this.colliders,
this.impulseJoints,
this.multibodyJoints,
this.ccdSolver,
eventQueue,
hooks,
);
}
/**
* Update colliders positions after rigid-bodies moved.
*
* When a rigid-body moves, the positions of the colliders attached to it need to be updated. This update is
* generally automatically done at the beginning and the end of each simulation step with World.step.
* If the positions need to be updated without running a simulation step this method can be called manually.
*/
public propagateModifiedBodyPositionsToColliders() {
this.bodies.raw.propagateModifiedBodyPositionsToColliders(
this.colliders.raw,
);
}
// TODO: This needs to trigger a broad-phase update but without emitting collision events?
// /**
// * Ensure subsequent scene queries take into account the collider positions set before this method is called.
// *
// * This does not step the physics simulation forward.
// */
// public updateSceneQueries() {
// this.propagateModifiedBodyPositionsToColliders();
// this.queryPipeline.update(this.colliders);
// }
/**
* The current simulation timestep.
*/
get timestep(): number {
return this.integrationParameters.dt;
}
/**
* Sets the new simulation timestep.
*
* The simulation timestep governs by how much the physics state of the world will
* be integrated. A simulation timestep should:
* - be as small as possible. Typical values evolve around 0.016 (assuming the chosen unit is milliseconds,
* corresponds to the time between two frames of a game running at 60FPS).
* - not vary too much during the course of the simulation. A timestep with large variations may
* cause instabilities in the simulation.
*
* @param dt - The timestep length, in seconds.
*/
set timestep(dt: number) {
this.integrationParameters.dt = dt;
}
/**
* The approximate size of most dynamic objects in the scene.
*
* See the documentation of the `World.lengthUnit` setter for further details.
*/
get lengthUnit(): number {
return this.integrationParameters.lengthUnit;
}
/**
* The approximate size of most dynamic objects in the scene.
*
* This value is used internally to estimate some length-based tolerance. In particular, the
* values `IntegrationParameters.allowedLinearError`,
* `IntegrationParameters.maxPenetrationCorrection`,
* `IntegrationParameters.predictionDistance`, `RigidBodyActivation.linearThreshold`
* are scaled by this value implicitly.
*
* This value can be understood as the number of units-per-meter in your physical world compared
* to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100
* pixels, set the `[`Self::length_unit`]` parameter to 100.0. The physics engine will interpret
* it as if 100 pixels is equivalent to 1 meter in its various internal threshold.
* (default `1.0`).
*/
set lengthUnit(unitsPerMeter: number) {
this.integrationParameters.lengthUnit = unitsPerMeter;
}
/**
* The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
*/
get numSolverIterations(): number {
return this.integrationParameters.numSolverIterations;
}
/**
* Sets the number of solver iterations run by the constraints solver for calculating forces (default: `4`).
*
* The greater this value is, the most rigid and realistic the physics simulation will be.
* However a greater number of iterations is more computationally intensive.
*
* @param niter - The new number of solver iterations.
*/
set numSolverIterations(niter: number) {
this.integrationParameters.numSolverIterations = niter;
}
/**
* Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
*/
get numInternalPgsIterations(): number {
return this.integrationParameters.numInternalPgsIterations;
}
/**
* Sets the Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
*
* Increasing this parameter will improve stability of the simulation. It will have a lesser effect than
* increasing `numSolverIterations` but is also less computationally expensive.
*
* @param niter - The new number of internal PGS iterations.
*/
set numInternalPgsIterations(niter: number) {
this.integrationParameters.numInternalPgsIterations = niter;
}
/**
* The number of substeps continuous collision-detection can run (default: `1`).
*/
get maxCcdSubsteps(): number {
return this.integrationParameters.maxCcdSubsteps;
}
/**
* Sets the number of substeps continuous collision-detection can run (default: `1`).
*
* CCD operates using a "motion clamping" mechanism where all fast-moving object trajectories will
* be truncated to their first impact on their path. The number of CCD substeps beyond 1 indicate how
* many times that trajectory will be updated and continued after a hit. This can results in smoother
* paths, but at a significant computational cost.
*
* @param niter - The new maximum number of CCD substeps. Setting to `0` disables CCD entirely.
*/
set maxCcdSubsteps(substeps: number) {
this.integrationParameters.maxCcdSubsteps = substeps;
}
/**
* Creates a new rigid-body from the given rigid-body descriptor.
*
* @param body - The description of the rigid-body to create.
*/
public createRigidBody(body: RigidBodyDesc): RigidBody {
return this.bodies.createRigidBody(this.colliders, body);
}
/**
* Creates a new character controller.
*
* @param offset - The artificial gap added between the characters chape and its environment.
*/
public createCharacterController(
offset: number,
): KinematicCharacterController {
let controller = new KinematicCharacterController(
offset,
this.integrationParameters,
this.broadPhase,
this.narrowPhase,
this.bodies,
this.colliders,
);
this.characterControllers.add(controller);
return controller;
}
/**
* Removes a character controller from this world.
*
* @param controller - The character controller to remove.
*/
public removeCharacterController(controller: KinematicCharacterController) {
this.characterControllers.delete(controller);
controller.free();
}
/**
* Creates a new PID (Proportional-Integral-Derivative) controller.
*
* @param kp - The Proportional gain applied to the instantaneous linear position errors.
* This is usually set to a multiple of the inverse of simulation step time
* (e.g. `60` if the delta-time is `1.0 / 60.0`).
* @param ki - The linear gain applied to the Integral part of the PID controller.
* @param kd - The Derivative gain applied to the instantaneous linear velocity errors.
* This is usually set to a value in `[0.0, 1.0]` where `0.0` implies no damping
* (no correction of velocity errors) and `1.0` implies complete damping (velocity errors
* are corrected in a single simulation step).
* @param axes - The axes affected by this controller.
* Only coordinate axes with a bit flags set to `true` will be taken into
* account when calculating the errors and corrections.
*/
public createPidController(
kp: number,
ki: number,
kd: number,
axes: PidAxesMask,
): PidController {
let controller = new PidController(
this.integrationParameters,
this.bodies,
kp,
ki,
kd,
axes,
);
this.pidControllers.add(controller);
return controller;
}
/**
* Removes a PID controller from this world.
*
* @param controller - The PID controller to remove.
*/
public removePidController(controller: PidController) {
this.pidControllers.delete(controller);
controller.free();
}
/**
* Creates a new collider.
*
* @param desc - The description of the collider.
* @param parent - The rigid-body this collider is attached to.
*/
public createCollider(desc: ColliderDesc, parent?: RigidBody): Collider {
let parentHandle = parent ? parent.handle : undefined;
return this.colliders.createCollider(this.bodies, desc, parentHandle);
}
/**
* Creates a new impulse joint from the given joint descriptor.
*
* @param params - The description of the joint to create.
* @param parent1 - The first rigid-body attached to this joint.
* @param parent2 - The second rigid-body attached to this joint.
* @param wakeUp - Should the attached rigid-bodies be awakened?
*/
public createImpulseJoint(
params: JointData,
parent1: RigidBody,
parent2: RigidBody,
wakeUp: boolean,
): ImpulseJoint {
return this.impulseJoints.createJoint(
this.bodies,
params,
parent1.handle,
parent2.handle,
wakeUp,
);
}
/**
* Creates a new multibody joint from the given joint descriptor.
*
* @param params - The description of the joint to create.
* @param parent1 - The first rigid-body attached to this joint.
* @param parent2 - The second rigid-body attached to this joint.
* @param wakeUp - Should the attached rigid-bodies be awakened?
*/
public createMultibodyJoint(
params: JointData,
parent1: RigidBody,
parent2: RigidBody,
wakeUp: boolean,
): MultibodyJoint {
return this.multibodyJoints.createJoint(
params,
parent1.handle,
parent2.handle,
wakeUp,
);
}
/**
* Retrieves a rigid-body from its handle.
*
* @param handle - The integer handle of the rigid-body to retrieve.
*/
public getRigidBody(handle: RigidBodyHandle): RigidBody {
return this.bodies.get(handle);
}
/**
* Retrieves a collider from its handle.
*
* @param handle - The integer handle of the collider to retrieve.
*/
public getCollider(handle: ColliderHandle): Collider {
return this.colliders.get(handle);
}
/**
* Retrieves an impulse joint from its handle.
*
* @param handle - The integer handle of the impulse joint to retrieve.
*/
public getImpulseJoint(handle: ImpulseJointHandle): ImpulseJoint {
return this.impulseJoints.get(handle);
}
/**
* Retrieves an multibody joint from its handle.
*
* @param handle - The integer handle of the multibody joint to retrieve.
*/
public getMultibodyJoint(handle: MultibodyJointHandle): MultibodyJoint {
return this.multibodyJoints.get(handle);
}
/**
* Removes the given rigid-body from this physics world.
*
* This will remove this rigid-body as well as all its attached colliders and joints.
* Every other bodies touching or attached by joints to this rigid-body will be woken-up.
*
* @param body - The rigid-body to remove.
*/
public removeRigidBody(body: RigidBody) {
if (this.bodies) {
this.bodies.remove(
body.handle,
this.islands,
this.colliders,
this.impulseJoints,
this.multibodyJoints,
);
}
}
/**
* Removes the given collider from this physics world.
*
* @param collider - The collider to remove.
* @param wakeUp - If set to `true`, the rigid-body this collider is attached to will be awaken.
*/
public removeCollider(collider: Collider, wakeUp: boolean) {
if (this.colliders) {
this.colliders.remove(
collider.handle,
this.islands,
this.bodies,
wakeUp,
);
}
}
/**
* Removes the given impulse joint from this physics world.
*
* @param joint - The impulse joint to remove.
* @param wakeUp - If set to `true`, the rigid-bodies attached by this joint will be awaken.
*/
public removeImpulseJoint(joint: ImpulseJoint, wakeUp: boolean) {
if (this.impulseJoints) {
this.impulseJoints.remove(joint.handle, wakeUp);
}
}
/**
* Removes the given multibody joint from this physics world.
*
* @param joint - The multibody joint to remove.
* @param wakeUp - If set to `true`, the rigid-bodies attached by this joint will be awaken.
*/
public removeMultibodyJoint(joint: MultibodyJoint, wakeUp: boolean) {
if (this.impulseJoints) {
this.multibodyJoints.remove(joint.handle, wakeUp);
}
}
/**
* Applies the given closure to each collider managed by this physics world.
*
* @param f(collider) - The function to apply to each collider managed by this physics world. Called as `f(collider)`.
*/
public forEachCollider(f: (collider: Collider) => void) {
this.colliders.forEach(f);
}
/**
* Applies the given closure to each rigid-body managed by this physics world.
*
* @param f(body) - The function to apply to each rigid-body managed by this physics world. Called as `f(collider)`.
*/
public forEachRigidBody(f: (body: RigidBody) => void) {
this.bodies.forEach(f);
}
/**
* Applies the given closure to each active rigid-body managed by this physics world.
*
* After a short time of inactivity, a rigid-body is automatically deactivated ("asleep") by
* the physics engine in order to save computational power. A sleeping rigid-body never moves
* unless it is moved manually by the user.
*
* @param f - The function to apply to each active rigid-body managed by this physics world. Called as `f(collider)`.
*/
public forEachActiveRigidBody(f: (body: RigidBody) => void) {
this.bodies.forEachActiveRigidBody(this.islands, f);
}
/**
* Find the closest intersection between a ray and the physics world.
*
* @param ray - The ray to cast.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the length of the ray to `ray.dir.norm() * maxToi`.
* @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
* origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
* whereas `false` implies that all shapes are hollow for this ray-cast.
* @param groups - Used to filter the colliders that can or cannot be hit by the ray.
* @param filter - The callback to filter out which collider will be hit.
*/
public castRay(
ray: Ray,
maxToi: number,
solid: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
): RayColliderHit | null {
return this.broadPhase.castRay(
this.narrowPhase,
this.bodies,
this.colliders,
ray,
maxToi,
solid,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Find the closest intersection between a ray and the physics world.
*
* This also computes the normal at the hit point.
* @param ray - The ray to cast.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the length of the ray to `ray.dir.norm() * maxToi`.
* @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
* origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
* whereas `false` implies that all shapes are hollow for this ray-cast.
* @param groups - Used to filter the colliders that can or cannot be hit by the ray.
*/
public castRayAndGetNormal(
ray: Ray,
maxToi: number,
solid: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
): RayColliderIntersection | null {
return this.broadPhase.castRayAndGetNormal(
this.narrowPhase,
this.bodies,
this.colliders,
ray,
maxToi,
solid,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Cast a ray and collects all the intersections between a ray and the scene.
*
* @param ray - The ray to cast.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the length of the ray to `ray.dir.norm() * maxToi`.
* @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
* origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
* whereas `false` implies that all shapes are hollow for this ray-cast.
* @param groups - Used to filter the colliders that can or cannot be hit by the ray.
* @param callback - The callback called once per hit (in no particular order) between a ray and a collider.
* If this callback returns `false`, then the cast will stop and no further hits will be detected/reported.
*/
public intersectionsWithRay(
ray: Ray,
maxToi: number,
solid: boolean,
callback: (intersect: RayColliderIntersection) => boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
) {
this.broadPhase.intersectionsWithRay(
this.narrowPhase,
this.bodies,
this.colliders,
ray,
maxToi,
solid,
callback,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Gets the handle of up to one collider intersecting the given shape.
*
* @param shapePos - The position of the shape used for the intersection test.
* @param shapeRot - The orientation of the shape used for the intersection test.
* @param shape - The shape used for the intersection test.
* @param groups - The bit groups and filter associated to the ray, in order to only
* hit the colliders with collision groups compatible with the ray's group.
*/
public intersectionWithShape(
shapePos: Vector,
shapeRot: Rotation,
shape: Shape,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
): Collider | null {
let handle = this.broadPhase.intersectionWithShape(
this.narrowPhase,
this.bodies,
this.colliders,
shapePos,
shapeRot,
shape,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
return handle != null ? this.colliders.get(handle) : null;
}
/**
* Find the projection of a point on the closest collider.
*
* @param point - The point to project.
* @param solid - If this is set to `true` then the collider shapes are considered to
* be plain (if the point is located inside of a plain shape, its projection is the point
* itself). If it is set to `false` the collider shapes are considered to be hollow
* (if the point is located inside of an hollow shape, it is projected on the shape's
* boundary).
* @param groups - The bit groups and filter associated to the point to project, in order to only
* project on colliders with collision groups compatible with the ray's group.
*/
public projectPoint(
point: Vector,
solid: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
): PointColliderProjection | null {
return this.broadPhase.projectPoint(
this.narrowPhase,
this.bodies,
this.colliders,
point,
solid,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Find the projection of a point on the closest collider.
*
* @param point - The point to project.
* @param groups - The bit groups and filter associated to the point to project, in order to only
* project on colliders with collision groups compatible with the ray's group.
*/
public projectPointAndGetFeature(
point: Vector,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
): PointColliderProjection | null {
return this.broadPhase.projectPointAndGetFeature(
this.narrowPhase,
this.bodies,
this.colliders,
point,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Find all the colliders containing the given point.
*
* @param point - The point used for the containment test.
* @param groups - The bit groups and filter associated to the point to test, in order to only
* test on colliders with collision groups compatible with the ray's group.
* @param callback - A function called with the handles of each collider with a shape
* containing the `point`.
*/
public intersectionsWithPoint(
point: Vector,
callback: (handle: Collider) => boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
) {
this.broadPhase.intersectionsWithPoint(
this.narrowPhase,
this.bodies,
this.colliders,
point,
this.colliders.castClosure(callback),
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Casts a shape at a constant linear velocity and retrieve the first collider it hits.
* This is similar to ray-casting except that we are casting a whole shape instead of
* just a point (the ray origin).
*
* @param shapePos - The initial position of the shape to cast.
* @param shapeRot - The initial rotation of the shape to cast.
* @param shapeVel - The constant velocity of the shape to cast (i.e. the cast direction).
* @param shape - The shape to cast.
* @param targetDistance If the shape moves closer to this distance from a collider, a hit
* will be returned.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the distance traveled by the shape to `shapeVel.norm() * maxToi`.
* @param stopAtPenetration - If set to `false`, the linear shape-cast wont immediately stop if
* the shape is penetrating another shape at its starting point **and** its trajectory is such
* that its on a path to exit that penetration state.
* @param groups - The bit groups and filter associated to the shape to cast, in order to only
* test on colliders with collision groups compatible with this group.
*/
public castShape(
shapePos: Vector,
shapeRot: Rotation,
shapeVel: Vector,
shape: Shape,
targetDistance: number,
maxToi: number,
stopAtPenetration: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
): ColliderShapeCastHit | null {
return this.broadPhase.castShape(
this.narrowPhase,
this.bodies,
this.colliders,
shapePos,
shapeRot,
shapeVel,
shape,
targetDistance,
maxToi,
stopAtPenetration,
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Retrieve all the colliders intersecting the given shape.
*
* @param shapePos - The position of the shape to test.
* @param shapeRot - The orientation of the shape to test.
* @param shape - The shape to test.
* @param groups - The bit groups and filter associated to the shape to test, in order to only
* test on colliders with collision groups compatible with this group.
* @param callback - A function called with the handles of each collider intersecting the `shape`.
*/
public intersectionsWithShape(
shapePos: Vector,
shapeRot: Rotation,
shape: Shape,
callback: (collider: Collider) => boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: Collider,
filterExcludeRigidBody?: RigidBody,
filterPredicate?: (collider: Collider) => boolean,
) {
this.broadPhase.intersectionsWithShape(
this.narrowPhase,
this.bodies,
this.colliders,
shapePos,
shapeRot,
shape,
this.colliders.castClosure(callback),
filterFlags,
filterGroups,
filterExcludeCollider ? filterExcludeCollider.handle : null,
filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
this.colliders.castClosure(filterPredicate),
);
}
/**
* Finds the handles of all the colliders with an AABB intersecting the given AABB.
*
* @param aabbCenter - The center of the AABB to test.
* @param aabbHalfExtents - The half-extents of the AABB to test.
* @param callback - The callback that will be called with the handles of all the colliders
* currently intersecting the given AABB.
*/
public collidersWithAabbIntersectingAabb(
aabbCenter: Vector,
aabbHalfExtents: Vector,
callback: (handle: Collider) => boolean,
) {
this.broadPhase.collidersWithAabbIntersectingAabb(
this.narrowPhase,
this.bodies,
this.colliders,
aabbCenter,
aabbHalfExtents,
this.colliders.castClosure(callback),
);
}
/**
* Enumerates all the colliders potentially in contact with the given collider.
*
* @param collider1 - The second collider involved in the contact.
* @param f - Closure that will be called on each collider that is in contact with `collider1`.
*/
public contactPairsWith(
collider1: Collider,
f: (collider2: Collider) => void,
) {
this.narrowPhase.contactPairsWith(
collider1.handle,
this.colliders.castClosure(f),
);
}
/**
* Enumerates all the colliders intersecting the given colliders, assuming one of them
* is a sensor.
*/
public intersectionPairsWith(
collider1: Collider,
f: (collider2: Collider) => void,
) {
this.narrowPhase.intersectionPairsWith(
collider1.handle,
this.colliders.castClosure(f),
);
}
/**
* Iterates through all the contact manifolds between the given pair of colliders.
*
* @param collider1 - The first collider involved in the contact.
* @param collider2 - The second collider involved in the contact.
* @param f - Closure that will be called on each contact manifold between the two colliders. If the second argument
* passed to this closure is `true`, then the contact manifold data is flipped, i.e., methods like `localNormal1`
* actually apply to the `collider2` and fields like `localNormal2` apply to the `collider1`.
*/
public contactPair(
collider1: Collider,
collider2: Collider,
f: (manifold: TempContactManifold, flipped: boolean) => void,
) {
this.narrowPhase.contactPair(collider1.handle, collider2.handle, f);
}
/**
* Returns `true` if `collider1` and `collider2` intersect and at least one of them is a sensor.
* @param collider1 The first collider involved in the intersection.
* @param collider2 The second collider involved in the intersection.
*/
public intersectionPair(collider1: Collider, collider2: Collider): boolean {
return this.narrowPhase.intersectionPair(
collider1.handle,
collider2.handle,
);
}
/**
* Sets whether internal performance profiling is enabled (default: false).
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
set profilerEnabled(enabled: boolean) {
this.physicsPipeline.raw.set_profiler_enabled(enabled);
}
/**
* Indicates if the internal performance profiling is enabled.
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
get profilerEnabled(): boolean {
return this.physicsPipeline.raw.is_profiler_enabled();
}
/**
* The time spent in milliseconds by the last step to run the entire simulation step.
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingStep(): number {
return this.physicsPipeline.raw.timing_step();
}
/**
* The time spent in milliseconds by the last step to run the collision-detection
* (broad-phase + narrow-phase).
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingCollisionDetection(): number {
return this.physicsPipeline.raw.timing_collision_detection();
}
/**
* The time spent in milliseconds by the last step to run the broad-phase.
*
* This timing is included in `timingCollisionDetection`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingBroadPhase(): number {
return this.physicsPipeline.raw.timing_broad_phase();
}
/**
* The time spent in milliseconds by the last step to run the narrow-phase.
*
* This timing is included in `timingCollisionDetection`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingNarrowPhase(): number {
return this.physicsPipeline.raw.timing_narrow_phase();
}
/**
* The time spent in milliseconds by the last step to run the constraint solver.
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingSolver(): number {
return this.physicsPipeline.raw.timing_solver();
}
/**
* The time spent in milliseconds by the last step to run the constraint
* initialization.
*
* This timing is included in `timingSolver`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingVelocityAssembly(): number {
return this.physicsPipeline.raw.timing_velocity_assembly();
}
/**
* The time spent in milliseconds by the last step to run the constraint
* resolution.
*
* This timing is included in `timingSolver`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingVelocityResolution(): number {
return this.physicsPipeline.raw.timing_velocity_resolution();
}
/**
* The time spent in milliseconds by the last step to run the rigid-body
* velocity update.
*
* This timing is included in `timingSolver`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingVelocityUpdate(): number {
return this.physicsPipeline.raw.timing_velocity_update();
}
/**
* The time spent in milliseconds by writing rigid-body velocities
* calculated by the solver back into the rigid-bodies.
*
* This timing is included in `timingSolver`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingVelocityWriteback(): number {
return this.physicsPipeline.raw.timing_velocity_writeback();
}
/**
* The total time spent in CCD detection and resolution.
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingCcd(): number {
return this.physicsPipeline.raw.timing_ccd();
}
/**
* The total time spent searching for the continuous hits during CCD.
*
* This timing is included in `timingCcd`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingCcdToiComputation(): number {
return this.physicsPipeline.raw.timing_ccd_toi_computation();
}
/**
* The total time spent in the broad-phase during CCD.
*
* This timing is included in `timingCcd`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingCcdBroadPhase(): number {
return this.physicsPipeline.raw.timing_ccd_broad_phase();
}
/**
* The total time spent in the narrow-phase during CCD.
*
* This timing is included in `timingCcd`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingCcdNarrowPhase(): number {
return this.physicsPipeline.raw.timing_ccd_narrow_phase();
}
/**
* The total time spent in the constraints resolution during CCD.
*
* This timing is included in `timingCcd`.
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingCcdSolver(): number {
return this.physicsPipeline.raw.timing_ccd_solver();
}
/**
* The total time spent in the islands calculation during CCD.
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingIslandConstruction(): number {
return this.physicsPipeline.raw.timing_island_construction();
}
/**
* The total time spent propagating detected user changes.
*
* Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
*/
public timingUserChanges(): number {
return this.physicsPipeline.raw.timing_user_changes();
}
}