/** * 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 pc.Entity('hinge'); * hinge.setPosition(1, 1, 0); * hinge.setEulerAngles(0, 0, 90); * hinge.addComponent('joint', { * type: pc.JOINTTYPE_HINGE, * entityA: door, * entityB: doorFrame, * enableLimits: true, * limits: new pc.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 Ammo constraint, when created. * * @type {object|null} * @private */ private _constraint; /** @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 an Ammo transform * owned by the caller. 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 {Mat4} [axisCorrection] - Optional rotation mapping the joint's X axis onto the * Bullet constraint's native axis. * @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. * @returns {object} The frame as a new Ammo.btTransform. * @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} 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';