playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
729 lines (728 loc) • 26.2 kB
TypeScript
/**
* The JointComponent constrains the relative motion of two rigid bodies. The entity holding the
* joint component is not itself constrained - instead, its world transform defines the joint
* frame: the anchor point and axes that the constraint operates about. The constrained bodies are
* assigned via {@link entityA} and {@link entityB}, both of which must have a
* {@link RigidBodyComponent}. If {@link entityB} is null, {@link entityA} is constrained to a
* fixed point in world space.
*
* A joint's primary axis is the joint entity's local X axis: a hinge rotates about X, a slider
* translates along X and a ball joint twists about X. To aim a joint, rotate the joint entity. A
* common pattern is to parent the joint entity to {@link entityA} at the pivot point. For 6dof
* joints, each degree of freedom measures the offset of {@link entityB} (or of the world anchor
* when {@link entityB} is null) relative to {@link entityA}, along the joint's axes.
*
* The joint frames are captured when the underlying constraint is created - typically when the
* component is enabled and both bodies are present in the physics simulation. Moving the joint
* entity afterwards has no effect on an existing constraint. Call {@link refreshFrames} to
* re-capture the frames from the current world transforms. Entity scale is ignored, matching the
* behavior of rigid bodies.
*
* Many properties apply only to specific joint types; each one documents the types it affects,
* and properties without such a note (for example {@link entityA}, {@link enableCollision} and
* {@link breakImpulse}) apply to all types.
*
* To add a JointComponent to an {@link Entity}, use {@link Entity#addComponent}:
*
* ```javascript
* // Create a door hinge: the joint entity's position is the hinge point and its
* // local X axis (rotated here to point up) is the hinge axis
* const hinge = new Entity('hinge');
* hinge.setPosition(1, 1, 0);
* hinge.setEulerAngles(0, 0, 90);
* hinge.addComponent('joint', {
* type: JOINTTYPE_HINGE,
* entityA: door,
* entityB: doorFrame,
* enableLimits: true,
* limits: new Vec2(0, 110)
* });
* app.root.addChild(hinge);
* ```
*
* @hideconstructor
* @category Physics
* @alpha
*/
export class JointComponent extends Component {
/**
* Fired when the applied impulse on the joint exceeds {@link breakImpulse} and the constraint
* breaks. The broken joint no longer constrains its bodies and {@link isBroken} becomes true.
* Call {@link refreshFrames} to re-attach it. Note that on ammo builds that expose no
* constraint state, breakage of 6dof joints cannot be detected, so this event does not fire
* for them - other joint types are unaffected.
*
* @event
* @example
* entity.joint.on('break', () => {
* console.log('The joint broke');
* });
*/
static EVENT_BREAK: string;
/**
* The physics backend joint, when created.
*
* @type {PhysicsJoint|null}
* @private
*/
private _joint;
/** @private */
private _type;
/**
* @type {Entity|null}
* @private
*/
private _entityA;
/**
* @type {Entity|null}
* @private
*/
private _entityB;
/** @private */
private _enableCollision;
/** @private */
private _breakImpulse;
/** @private */
private _broken;
/** @private */
private _initialized;
/** @private */
private _enableLimits;
/** @private */
private _limits;
/** @private */
private _motorSpeed;
/** @private */
private _maxMotorForce;
/** @private */
private _swingLimitY;
/** @private */
private _swingLimitZ;
/** @private */
private _twistLimit;
/** @private */
private _linearMotionX;
/** @private */
private _linearMotionY;
/** @private */
private _linearMotionZ;
/** @private */
private _linearLimitsX;
/** @private */
private _linearLimitsY;
/** @private */
private _linearLimitsZ;
/** @private */
private _linearStiffness;
/** @private */
private _linearDamping;
/** @private */
private _linearEquilibrium;
/** @private */
private _angularMotionX;
/** @private */
private _angularMotionY;
/** @private */
private _angularMotionZ;
/** @private */
private _angularLimitsX;
/** @private */
private _angularLimitsY;
/** @private */
private _angularLimitsZ;
/** @private */
private _angularStiffness;
/** @private */
private _angularDamping;
/** @private */
private _angularEquilibrium;
/**
* @type {EventHandle|null}
* @private
*/
private _evtEntityADestroy;
/**
* @type {EventHandle|null}
* @private
*/
private _evtEntityBDestroy;
/**
* @type {EventHandle|null}
* @private
*/
private _evtSimEnabledA;
/**
* @type {EventHandle|null}
* @private
*/
private _evtSimEnabledB;
/**
* @type {EventHandle[]}
* @private
*/
private _evtBodyTeardown;
/** @private */
private _anchorA;
/** @private */
private _anchorB;
/** @private */
private _axisA;
/**
* Sets the type of joint. Can be:
*
* - {@link JOINTTYPE_FIXED}: rigidly locks the bodies together.
* - {@link JOINTTYPE_BALL}: ball and socket - free rotation about the anchor point, with
* optional swing and twist limits.
* - {@link JOINTTYPE_HINGE}: rotation about the joint's X axis, with optional limits and
* motor.
* - {@link JOINTTYPE_SLIDER}: translation along the joint's X axis, with optional limits and
* motor.
* - {@link JOINTTYPE_6DOF}: each linear and angular axis is independently locked, limited or
* free, with optional springs.
*
* Defaults to {@link JOINTTYPE_FIXED}.
*
* @type {JOINTTYPE_FIXED|JOINTTYPE_BALL|JOINTTYPE_HINGE|JOINTTYPE_SLIDER|JOINTTYPE_6DOF}
*/
set type(type: "fixed" | "ball" | "hinge" | "slider" | "6dof");
/**
* Gets the type of joint.
*
* @type {JOINTTYPE_FIXED|JOINTTYPE_BALL|JOINTTYPE_HINGE|JOINTTYPE_SLIDER|JOINTTYPE_6DOF}
*/
get type(): "fixed" | "ball" | "hinge" | "slider" | "6dof";
/**
* Sets the first entity constrained by this joint. The entity must have a
* {@link RigidBodyComponent}. Can be set to an {@link Entity} or the GUID of an entity. The
* constraint is created once both constrained entities have rigid bodies in the simulation.
*
* @type {Entity|string|null}
*/
set entityA(arg: Entity | null);
/**
* Gets the first entity constrained by this joint.
*
* @type {Entity|null}
*/
get entityA(): Entity | null;
/**
* Sets the second entity constrained by this joint. The entity must have a
* {@link RigidBodyComponent}. Can be set to an {@link Entity} or the GUID of an entity. If
* null, {@link entityA} is constrained to a fixed point in world space instead. Defaults to
* null.
*
* @type {Entity|string|null}
*/
set entityB(arg: Entity | null);
/**
* Gets the second entity constrained by this joint.
*
* @type {Entity|null}
*/
get entityB(): Entity | null;
/**
* Sets whether the two constrained bodies can collide with each other. Defaults to false.
*
* @type {boolean}
*/
set enableCollision(enableCollision: boolean);
/**
* Gets whether the two constrained bodies can collide with each other.
*
* @type {boolean}
*/
get enableCollision(): boolean;
/**
* Sets the impulse threshold, in Newton seconds, above which the joint breaks. A broken joint
* no longer constrains its bodies, fires the 'break' event and has {@link isBroken} set to
* true. As a rule of thumb, a steady force breaks the joint when force × simulation timestep
* exceeds this value. Defaults to Infinity (unbreakable).
*
* @type {number}
*/
set breakImpulse(impulse: number);
/**
* Gets the impulse threshold above which the joint breaks.
*
* @type {number}
*/
get breakImpulse(): number;
/**
* The underlying Ammo (Bullet) constraint, or null if it has not been created or has broken.
* An unsupported escape hatch for native functionality the component does not yet expose - it
* is deliberately kept off the public, backend-agnostic API surface, mirroring
* {@link RigidBodyComponent#body}.
*
* @type {object|null}
* @ignore
*/
get constraint(): object | null;
/**
* Gets whether the joint has broken as a result of the applied impulse exceeding
* {@link breakImpulse}. A broken joint is re-armed by calling {@link refreshFrames}, toggling
* {@link Component#enabled} or changing {@link type}, {@link entityA} or {@link entityB}.
*
* @type {boolean}
*/
get isBroken(): boolean;
/**
* Sets whether the joint's limits are enabled. For hinge joints, this limits rotation about
* the joint's X axis to {@link limits}. For slider joints, this limits travel along the
* joint's X axis to {@link limits}. For ball joints, this limits rotation to
* {@link swingLimitY}, {@link swingLimitZ} and {@link twistLimit}. Not used by fixed and 6dof
* joints. Defaults to false.
*
* @type {boolean}
*/
set enableLimits(arg: boolean);
/**
* Gets whether the joint's limits are enabled.
*
* @type {boolean}
*/
get enableLimits(): boolean;
/**
* Sets the lower and upper limit of the joint's primary degree of freedom. For hinge joints,
* these are angles of rotation about the joint's X axis, in degrees; for slider joints,
* distances along the joint's X axis, in meters. Only used by hinge and slider joints, when
* {@link enableLimits} is true. (Ball joints limit motion with {@link swingLimitY},
* {@link swingLimitZ} and {@link twistLimit}; 6dof joints use {@link linearLimitsX} and
* {@link angularLimitsX} etc.) Defaults to `[-45, 45]`.
*
* @type {Vec2}
*/
set limits(arg: Vec2);
/**
* Gets the lower and upper limit of the joint's primary degree of freedom.
*
* @type {Vec2}
*/
get limits(): Vec2;
/**
* Sets the target speed the motor drives towards. For hinge joints, this is an angular speed
* in degrees per second; for slider joints, a linear speed in meters per second. The motor is
* active only while {@link maxMotorForce} is greater than 0; with a target speed of 0 and a
* positive force the motor acts as a brake, holding the joint at rest. Only used by hinge and
* slider joints. Defaults to 0.
*
* @type {number}
*/
set motorSpeed(arg: number);
/**
* Gets the target speed the motor drives towards.
*
* @type {number}
*/
get motorSpeed(): number;
/**
* Sets the maximum force the motor can apply to reach {@link motorSpeed}. For hinge joints,
* this is a torque in Newton meters; for slider joints, a force in Newtons. The motor is
* disabled while this is 0, so set it greater than 0 to engage the motor. Only used by hinge
* and slider joints. Defaults to 0.
*
* @type {number}
*/
set maxMotorForce(arg: number);
/**
* Gets the maximum force the joint's motor can apply.
*
* @type {number}
*/
get maxMotorForce(): number;
/**
* Sets the maximum swing of a ball joint towards its Y axis, in degrees, measured as a
* half-angle either side of the joint's X axis. Only used by ball joints when
* {@link enableLimits} is true. Defaults to 45.
*
* @type {number}
*/
set swingLimitY(arg: number);
/**
* Gets the maximum swing of a ball joint towards its Y axis.
*
* @type {number}
*/
get swingLimitY(): number;
/**
* Sets the maximum swing of a ball joint towards its Z axis, in degrees, measured as a
* half-angle either side of the joint's X axis. Only used by ball joints when
* {@link enableLimits} is true. Defaults to 45.
*
* @type {number}
*/
set swingLimitZ(arg: number);
/**
* Gets the maximum swing of a ball joint towards its Z axis.
*
* @type {number}
*/
get swingLimitZ(): number;
/**
* Sets the maximum twist of a ball joint about its X axis, in degrees, measured as a
* half-angle either side of the rest orientation. Only used by ball joints when
* {@link enableLimits} is true. Defaults to 20.
*
* @type {number}
*/
set twistLimit(arg: number);
/**
* Gets the maximum twist of a ball joint about its X axis.
*
* @type {number}
*/
get twistLimit(): number;
/**
* Sets the type of motion allowed for translation along the joint's X axis. Can be
* {@link MOTION_LOCKED}, {@link MOTION_LIMITED} or {@link MOTION_FREE}. Only used by 6dof
* joints. Defaults to {@link MOTION_LOCKED}.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
set linearMotionX(arg: "free" | "limited" | "locked");
/**
* Gets the type of motion allowed for translation along the joint's X axis.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
get linearMotionX(): "free" | "limited" | "locked";
/**
* Sets the type of motion allowed for translation along the joint's Y axis. Can be
* {@link MOTION_LOCKED}, {@link MOTION_LIMITED} or {@link MOTION_FREE}. Only used by 6dof
* joints. Defaults to {@link MOTION_LOCKED}.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
set linearMotionY(arg: "free" | "limited" | "locked");
/**
* Gets the type of motion allowed for translation along the joint's Y axis.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
get linearMotionY(): "free" | "limited" | "locked";
/**
* Sets the type of motion allowed for translation along the joint's Z axis. Can be
* {@link MOTION_LOCKED}, {@link MOTION_LIMITED} or {@link MOTION_FREE}. Only used by 6dof
* joints. Defaults to {@link MOTION_LOCKED}.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
set linearMotionZ(arg: "free" | "limited" | "locked");
/**
* Gets the type of motion allowed for translation along the joint's Z axis.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
get linearMotionZ(): "free" | "limited" | "locked";
/**
* Sets the lower and upper limit of translation along the joint's X axis, in meters. Only
* used by 6dof joints when {@link linearMotionX} is {@link MOTION_LIMITED}. Defaults to
* `[0, 0]`.
*
* @type {Vec2}
*/
set linearLimitsX(arg: Vec2);
/**
* Gets the lower and upper limit of translation along the joint's X axis.
*
* @type {Vec2}
*/
get linearLimitsX(): Vec2;
/**
* Sets the lower and upper limit of translation along the joint's Y axis, in meters. Only
* used by 6dof joints when {@link linearMotionY} is {@link MOTION_LIMITED}. Defaults to
* `[0, 0]`.
*
* @type {Vec2}
*/
set linearLimitsY(arg: Vec2);
/**
* Gets the lower and upper limit of translation along the joint's Y axis.
*
* @type {Vec2}
*/
get linearLimitsY(): Vec2;
/**
* Sets the lower and upper limit of translation along the joint's Z axis, in meters. Only
* used by 6dof joints when {@link linearMotionZ} is {@link MOTION_LIMITED}. Defaults to
* `[0, 0]`.
*
* @type {Vec2}
*/
set linearLimitsZ(arg: Vec2);
/**
* Gets the lower and upper limit of translation along the joint's Z axis.
*
* @type {Vec2}
*/
get linearLimitsZ(): Vec2;
/**
* Sets the stiffness of the springs acting on translation along the joint's X, Y and Z axes.
* A spring acts on an axis when its stiffness component is greater than 0. Only used by 6dof
* joints. Defaults to `[0, 0, 0]` (no springs).
*
* @type {Vec3}
*/
set linearStiffness(arg: Vec3);
/**
* Gets the stiffness of the springs acting on translation along the joint's X, Y and Z axes.
*
* @type {Vec3}
*/
get linearStiffness(): Vec3;
/**
* Sets the damping of the springs acting on translation along the joint's X, Y and Z axes.
* Only used by 6dof joints. Defaults to `[1, 1, 1]`.
*
* @type {Vec3}
*/
set linearDamping(arg: Vec3);
/**
* Gets the damping of the springs acting on translation along the joint's X, Y and Z axes.
*
* @type {Vec3}
*/
get linearDamping(): Vec3;
/**
* Sets the rest positions of the springs acting on translation along the joint's X, Y and Z
* axes, in meters. Only used by 6dof joints. Defaults to `[0, 0, 0]`.
*
* @type {Vec3}
*/
set linearEquilibrium(arg: Vec3);
/**
* Gets the rest positions of the springs acting on translation along the joint's X, Y and Z
* axes.
*
* @type {Vec3}
*/
get linearEquilibrium(): Vec3;
/**
* Sets the type of motion allowed for rotation about the joint's X axis. Can be
* {@link MOTION_LOCKED}, {@link MOTION_LIMITED} or {@link MOTION_FREE}. Only used by 6dof
* joints. Defaults to {@link MOTION_LOCKED}.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
set angularMotionX(arg: "free" | "limited" | "locked");
/**
* Gets the type of motion allowed for rotation about the joint's X axis.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
get angularMotionX(): "free" | "limited" | "locked";
/**
* Sets the type of motion allowed for rotation about the joint's Y axis. Can be
* {@link MOTION_LOCKED}, {@link MOTION_LIMITED} or {@link MOTION_FREE}. Only used by 6dof
* joints. Defaults to {@link MOTION_LOCKED}.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
set angularMotionY(arg: "free" | "limited" | "locked");
/**
* Gets the type of motion allowed for rotation about the joint's Y axis.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
get angularMotionY(): "free" | "limited" | "locked";
/**
* Sets the type of motion allowed for rotation about the joint's Z axis. Can be
* {@link MOTION_LOCKED}, {@link MOTION_LIMITED} or {@link MOTION_FREE}. Only used by 6dof
* joints. Defaults to {@link MOTION_LOCKED}.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
set angularMotionZ(arg: "free" | "limited" | "locked");
/**
* Gets the type of motion allowed for rotation about the joint's Z axis.
*
* @type {MOTION_FREE|MOTION_LIMITED|MOTION_LOCKED}
*/
get angularMotionZ(): "free" | "limited" | "locked";
/**
* Sets the lower and upper limit of rotation about the joint's X axis, in degrees. For
* stability, keep the limits within -180 and 180 degrees. Only used by 6dof joints when
* {@link angularMotionX} is {@link MOTION_LIMITED}. Defaults to `[0, 0]`.
*
* @type {Vec2}
*/
set angularLimitsX(arg: Vec2);
/**
* Gets the lower and upper limit of rotation about the joint's X axis.
*
* @type {Vec2}
*/
get angularLimitsX(): Vec2;
/**
* Sets the lower and upper limit of rotation about the joint's Y axis, in degrees. For
* stability, keep the limits within -90 and 90 degrees. Only used by 6dof joints when
* {@link angularMotionY} is {@link MOTION_LIMITED}. Defaults to `[0, 0]`.
*
* @type {Vec2}
*/
set angularLimitsY(arg: Vec2);
/**
* Gets the lower and upper limit of rotation about the joint's Y axis.
*
* @type {Vec2}
*/
get angularLimitsY(): Vec2;
/**
* Sets the lower and upper limit of rotation about the joint's Z axis, in degrees. For
* stability, keep the limits within -180 and 180 degrees. Only used by 6dof joints when
* {@link angularMotionZ} is {@link MOTION_LIMITED}. Defaults to `[0, 0]`.
*
* @type {Vec2}
*/
set angularLimitsZ(arg: Vec2);
/**
* Gets the lower and upper limit of rotation about the joint's Z axis.
*
* @type {Vec2}
*/
get angularLimitsZ(): Vec2;
/**
* Sets the stiffness of the springs acting on rotation about the joint's X, Y and Z axes. A
* spring acts on an axis when its stiffness component is greater than 0. Only used by 6dof
* joints. Defaults to `[0, 0, 0]` (no springs).
*
* @type {Vec3}
*/
set angularStiffness(arg: Vec3);
/**
* Gets the stiffness of the springs acting on rotation about the joint's X, Y and Z axes.
*
* @type {Vec3}
*/
get angularStiffness(): Vec3;
/**
* Sets the damping of the springs acting on rotation about the joint's X, Y and Z axes. Only
* used by 6dof joints. Defaults to `[1, 1, 1]`.
*
* @type {Vec3}
*/
set angularDamping(arg: Vec3);
/**
* Gets the damping of the springs acting on rotation about the joint's X, Y and Z axes.
*
* @type {Vec3}
*/
get angularDamping(): Vec3;
/**
* Sets the rest angles of the springs acting on rotation about the joint's X, Y and Z axes,
* in degrees. Only used by 6dof joints. Defaults to `[0, 0, 0]`.
*
* @type {Vec3}
*/
set angularEquilibrium(arg: Vec3);
/**
* Gets the rest angles of the springs acting on rotation about the joint's X, Y and Z axes.
*
* @type {Vec3}
*/
get angularEquilibrium(): Vec3;
/**
* Destroys and recreates the underlying constraint, re-capturing the joint frames from the
* current world transforms of the joint entity, {@link entityA} and {@link entityB}. Call
* this after moving the joint entity to re-anchor the joint, or to re-attach a joint that has
* broken.
*/
refreshFrames(): void;
/**
* Resolves an entity reference, accepting an Entity instance or a GUID string, and tracks the
* destruction of the referenced entity.
*
* @param {'_entityA'|'_entityB'} prop - The backing field to assign.
* @param {'_evtEntityADestroy'|'_evtEntityBDestroy'} evtProp - The destroy event handle field.
* @param {Entity|string|null} arg - The entity, entity GUID or null.
* @private
*/
private _setJointEntity;
/**
* @param {Entity} entity - The entity to check.
* @returns {boolean} True if the entity has a rigid body that is present in the simulation.
* @private
*/
private _isBodyReady;
/** @private */
private _subscribeBodyAvailable;
/** @private */
private _clearBodyAvailableSubscriptions;
/** @private */
private _onBodyAvailable;
/** @private */
private _onBodyLost;
/**
* Creates the constraint if the component is in a state where one should exist and both
* constrained bodies are present in the simulation. Otherwise, the component registers with
* the system to retry when the bodies become available, which is also where this is called
* from.
*
* @ignore
*/
_tryCreateConstraint(): void;
/**
* Computes the joint frame in the local space of the given body entity, as a scale-free
* matrix with X as the primary joint axis. The frame's local origin and X axis are also
* written to anchor and axis, for use by break detection.
*
* @param {Entity|null} bodyEntity - The entity whose local space the frame is expressed in,
* or null for the world-pinning fixed body, whose transform is identity.
* @param {Vec3} anchor - Receives the frame origin in the body's local space.
* @param {Vec3} axis - Receives the frame X axis in the body's local space.
* @param {Mat4} frame - Receives the joint frame.
* @private
*/
private _createFrame;
/** @private */
private _createConstraint;
/** @private */
private _destroyConstraint;
/**
* Tests whether the joint frame anchors of the two bodies have drifted apart, which can only
* happen once bullet has internally disabled a broken constraint. Used for break detection
* on ammo builds that expose no constraint state. The slide axis of slider joints is ignored
* and 6dof joints, whose axes may all be free, are not detectable this way.
*
* @returns {boolean} True if the anchors have separated.
* @private
*/
private _isAnchorSeparated;
/**
* Tests whether the constraint has broken during the last simulation step and if so, fires
* the break event and destroys the constraint. Called by the system after each physics step
* for joints with a finite break impulse.
*
* @ignore
*/
_checkBroken(): void;
/**
* Wakes the constrained bodies so that a change to the constraint takes immediate effect on
* a sleeping simulation island.
*
* @private
*/
private _activateBodies;
/** @private */
private _updateLimits;
/** @private */
private _updateMotor;
/** @private */
private _updateSpring;
onBeforeRemove(): void;
/**
* Remaps entity references to their duplicated counterparts when the joint is cloned as part
* of an entity subtree. References to entities outside the duplicated subtree are preserved.
*
* @param {JointComponent} oldJoint - The joint component being duplicated.
* @param {Object<string, Entity>} duplicatedIdsMap - A map of original entity GUIDs to cloned
* entities.
* @ignore
*/
resolveDuplicatedEntityReferenceProperties(oldJoint: JointComponent, duplicatedIdsMap: {
[x: string]: Entity;
}): void;
}
import { Component } from '../component.js';
import type { Entity } from '../../entity.js';
import { Vec2 } from '../../../core/math/vec2.js';
import { Vec3 } from '../../../core/math/vec3.js';