From ca6485c5ee0795e7daef11d8a63e62783f570a27 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 23 Nov 2024 11:17:38 +0000 Subject: [PATCH 1/5] Beta release of JointComponent --- src/framework/components/joint/component.js | 975 ++++++++++++++++---- src/framework/components/joint/constants.js | 3 - src/framework/components/joint/system.js | 2 +- 3 files changed, 816 insertions(+), 164 deletions(-) diff --git a/src/framework/components/joint/component.js b/src/framework/components/joint/component.js index c556ac60a07..26b613289d8 100644 --- a/src/framework/components/joint/component.js +++ b/src/framework/components/joint/component.js @@ -28,11 +28,75 @@ const properties = [ ]; /** - * The JointComponent adds a physics joint constraint linking two rigid bodies. - * - * @ignore + * The JointComponent adds a physics joint constraint between two rigid body components. + * A joint connects two rigid bodies and restricts their relative movement in various ways. + * It supports both linear and angular constraints along all three axes, with options for: + * + * - Locked motion (no movement) + * - Free motion (unrestricted movement) + * - Limited motion (movement within specified limits) + * - Spring behavior (with configurable stiffness, damping, and equilibrium points) + * + * This can be used to create a variety of joint types like hinges, sliders, ball-and-socket joints, etc. + * Each degree of freedom (linear and angular) for each axis (X, Y, Z) can be configured independently. + * + * @beta */ class JointComponent extends Component { + _constraint = null; + _entityA = null; + _entityB = null; + _breakForce = 3.4e+38; + _enableCollision = true; + + // Linear X degree of freedom + _linearMotionX = MOTION_LOCKED; + _linearLimitsX = new Vec2(0, 0); + _linearSpringX = false; + _linearStiffnessX = 0; + _linearDampingX = 1; + _linearEquilibriumX = 0; + + // Linear Y degree of freedom + _linearMotionY = MOTION_LOCKED; + _linearLimitsY = new Vec2(0, 0); + _linearSpringY = false; + _linearStiffnessY = 0; + _linearDampingY = 1; + _linearEquilibriumY = 0; + + // Linear Z degree of freedom + _linearMotionZ = MOTION_LOCKED; + _linearLimitsZ = new Vec2(0, 0); + _linearSpringZ = false; + _linearStiffnessZ = 0; + _linearDampingZ = 1; + _linearEquilibriumZ = 0; + + // Angular X degree of freedom + _angularMotionX = MOTION_LOCKED; + _angularLimitsX = new Vec2(0, 0); + _angularSpringX = false; + _angularStiffnessX = 0; + _angularDampingX = 1; + _angularEquilibriumX = 0; + + // Angular Y degree of freedom + _angularMotionY = MOTION_LOCKED; + _angularLimitsY = new Vec2(0, 0); + _angularSpringY = false; + _angularStiffnessY = 0; + _angularDampingY = 1; + _angularEquilibriumY = 0; + + // Angular Z degree of freedom + _angularMotionZ = MOTION_LOCKED; + _angularLimitsZ = new Vec2(0, 0); + _angularSpringZ = false; + _angularEquilibriumZ = 0; + _angularDampingZ = 1; + _angularStiffnessZ = 0; + /** * Create a new JointComponent instance. * @@ -43,106 +107,142 @@ class JointComponent extends Component { super(system, entity); Debug.assert(typeof Ammo !== 'undefined', 'ERROR: Attempting to create a pc.JointComponent but Ammo.js is not loaded'); + } - this._constraint = null; - - this._entityA = null; - this._entityB = null; - this._breakForce = 3.4e+38; - this._enableCollision = true; - - // Linear X degree of freedom - this._linearMotionX = MOTION_LOCKED; - this._linearLimitsX = new Vec2(0, 0); - this._linearSpringX = false; - this._linearStiffnessX = 0; - this._linearDampingX = 1; - this._linearEquilibriumX = 0; - - // Linear Y degree of freedom - this._linearMotionY = MOTION_LOCKED; - this._linearLimitsY = new Vec2(0, 0); - this._linearSpringY = false; - this._linearStiffnessY = 0; - this._linearDampingY = 1; - this._linearEquilibriumY = 0; - - // Linear Z degree of freedom - this._linearMotionZ = MOTION_LOCKED; - this._linearLimitsZ = new Vec2(0, 0); - this._linearSpringZ = false; - this._linearStiffnessZ = 0; - this._linearDampingZ = 1; - this._linearEquilibriumZ = 0; - - // Angular X degree of freedom - this._angularMotionX = MOTION_LOCKED; - this._angularLimitsX = new Vec2(0, 0); - this._angularSpringX = false; - this._angularStiffnessX = 0; - this._angularDampingX = 1; - this._angularEquilibriumX = 0; - - // Angular Y degree of freedom - this._angularMotionY = MOTION_LOCKED; - this._angularLimitsY = new Vec2(0, 0); - this._angularSpringY = false; - this._angularStiffnessY = 0; - this._angularDampingY = 1; - this._angularEquilibriumY = 0; - - // Angular Z degree of freedom - this._angularMotionZ = MOTION_LOCKED; - this._angularLimitsZ = new Vec2(0, 0); - this._angularSpringZ = false; - this._angularEquilibriumZ = 0; - this._angularDampingZ = 1; - this._angularStiffnessZ = 0; - - this.on('set_enabled', this._onSetEnabled, this); + /** + * Sets the angular damping for the X-axis. This reduces the angular velocity over time. + * A value of 0 means no damping, while higher values cause more damping. + * + * @type {number} + */ + set angularDampingX(value) { + if (this._angularDampingX !== value) { + this._angularDampingX = value; + if (this._constraint) this._constraint.setDamping(3, value); + } } - set entityA(body) { - this._destroyConstraint(); - this._entityA = body; - this._createConstraint(); + /** + * Gets the angular damping for the X-axis. + * + * @type {number} + */ + get angularDampingX() { + return this._angularDampingX; } - get entityA() { - return this._entityA; + /** + * Sets the angular damping for the Y-axis. This reduces the angular velocity over time. + * A value of 0 means no damping, while higher values cause more damping. + * + * @type {number} + */ + set angularDampingY(value) { + if (this._angularDampingY !== value) { + this._angularDampingY = value; + if (this._constraint) this._constraint.setDamping(4, value); + } } - set entityB(body) { - this._destroyConstraint(); - this._entityB = body; - this._createConstraint(); + /** + * Gets the angular damping for the Y-axis. + * + * @type {number} + */ + get angularDampingY() { + return this._angularDampingY; } - get entityB() { - return this._entityB; + /** + * Sets the angular damping for the Z-axis. This reduces the angular velocity over time. + * A value of 0 means no damping, while higher values cause more damping. + * + * @type {number} + */ + set angularDampingZ(value) { + if (this._angularDampingZ !== value) { + this._angularDampingZ = value; + if (this._constraint) this._constraint.setDamping(5, value); + } } - set breakForce(force) { - if (this._constraint && this._breakForce !== force) { - this._constraint.setBreakingImpulseThreshold(force); - this._breakForce = force; + /** + * Gets the angular damping for the Z-axis. + * + * @type {number} + */ + get angularDampingZ() { + return this._angularDampingZ; + } + + /** + * Sets the angular equilibrium point for the X-axis. The spring will attempt to reach this angle. + * + * @type {number} + */ + set angularEquilibriumX(value) { + if (this._angularEquilibriumX !== value) { + this._angularEquilibriumX = value; + if (this._constraint) this._constraint.setEquilibriumPoint(3, value); } } - get breakForce() { - return this._breakForce; + /** + * Gets the angular equilibrium point for the X-axis. + * + * @type {number} + */ + get angularEquilibriumX() { + return this._angularEquilibriumX; } - set enableCollision(enableCollision) { - this._destroyConstraint(); - this._enableCollision = enableCollision; - this._createConstraint(); + /** + * Sets the angular equilibrium point for the Y-axis. The spring will attempt to reach this angle. + * + * @type {number} + */ + set angularEquilibriumY(value) { + if (this._angularEquilibriumY !== value) { + this._angularEquilibriumY = value; + if (this._constraint) this._constraint.setEquilibriumPoint(4, value); + } } - get enableCollision() { - return this._enableCollision; + /** + * Gets the angular equilibrium point for the Y-axis. + * + * @type {number} + */ + get angularEquilibriumY() { + return this._angularEquilibriumY; } + /** + * Sets the angular equilibrium point for the Z-axis. The spring will attempt to reach this angle. + * + * @type {number} + */ + set angularEquilibriumZ(value) { + if (this._angularEquilibriumZ !== value) { + this._angularEquilibriumZ = value; + if (this._constraint) this._constraint.setEquilibriumPoint(5, value); + } + } + + /** + * Gets the angular equilibrium point for the Z-axis. + * + * @type {number} + */ + get angularEquilibriumZ() { + return this._angularEquilibriumZ; + } + + /** + * Sets the angular limits for the X-axis rotation in degrees. + * + * @type {Vec2} + */ set angularLimitsX(limits) { if (!this._angularLimitsX.equals(limits)) { this._angularLimitsX.copy(limits); @@ -150,54 +250,113 @@ class JointComponent extends Component { } } + /** + * Gets the angular limits for the X-axis rotation. + * + * @type {Vec2} + */ get angularLimitsX() { return this._angularLimitsX; } - set angularMotionX(value) { - if (this._angularMotionX !== value) { - this._angularMotionX = value; + /** + * Sets the angular limits for the Y-axis rotation in degrees. + * + * @type {Vec2} + */ + set angularLimitsY(limits) { + if (!this._angularLimitsY.equals(limits)) { + this._angularLimitsY.copy(limits); this._updateAngularLimits(); } } - get angularMotionX() { - return this._angularMotionX; + /** + * Gets the angular limits for the Y-axis rotation. + * + * @type {Vec2} + */ + get angularLimitsY() { + return this._angularLimitsY; } - set angularLimitsY(limits) { - if (!this._angularLimitsY.equals(limits)) { - this._angularLimitsY.copy(limits); + /** + * Sets the angular limits for the Z-axis rotation in degrees. + * + * @type {Vec2} + */ + set angularLimitsZ(limits) { + if (!this._angularLimitsZ.equals(limits)) { + this._angularLimitsZ.copy(limits); this._updateAngularLimits(); } } - get angularLimitsY() { - return this._angularLimitsY; + /** + * Gets the angular limits for the Z-axis rotation. + * + * @type {Vec2} + */ + get angularLimitsZ() { + return this._angularLimitsZ; } - set angularMotionY(value) { - if (this._angularMotionY !== value) { - this._angularMotionY = value; + /** + * Sets the type of motion allowed for rotation around the X-axis. Can be: + * - MOTION_LOCKED: No rotation allowed + * - MOTION_FREE: Unlimited rotation allowed + * - MOTION_LIMITED: Rotation limited by angularLimitsX + * + * @type {string} + */ + set angularMotionX(value) { + if (this._angularMotionX !== value) { + this._angularMotionX = value; this._updateAngularLimits(); } } - get angularMotionY() { - return this._angularMotionY; + /** + * Gets the type of motion allowed for rotation around the X-axis. + * + * @type {string} + */ + get angularMotionX() { + return this._angularMotionX; } - set angularLimitsZ(limits) { - if (!this._angularLimitsZ.equals(limits)) { - this._angularLimitsZ.copy(limits); + /** + * Sets the type of motion allowed for rotation around the Y-axis. Can be: + * - MOTION_LOCKED: No rotation allowed + * - MOTION_FREE: Unlimited rotation allowed + * - MOTION_LIMITED: Rotation limited by angularLimitsY + * + * @type {string} + */ + set angularMotionY(value) { + if (this._angularMotionY !== value) { + this._angularMotionY = value; this._updateAngularLimits(); } } - get angularLimitsZ() { - return this._angularLimitsZ; + /** + * Gets the type of motion allowed for rotation around the Y-axis. + * + * @type {string} + */ + get angularMotionY() { + return this._angularMotionY; } + /** + * Sets the type of motion allowed for rotation around the Z-axis. Can be: + * - MOTION_LOCKED: No rotation allowed + * - MOTION_FREE: Unlimited rotation allowed + * - MOTION_LIMITED: Rotation limited by angularLimitsZ + * + * @type {string} + */ set angularMotionZ(value) { if (this._angularMotionZ !== value) { this._angularMotionZ = value; @@ -205,10 +364,353 @@ class JointComponent extends Component { } } + /** + * Gets the type of motion allowed for rotation around the Z-axis. + * + * @type {string} + */ get angularMotionZ() { return this._angularMotionZ; } + /** + * Enables or disables the spring behavior for rotation around the X-axis. + * + * @type {boolean} + */ + set angularSpringX(value) { + if (this._angularSpringX !== value) { + this._angularSpringX = value; + if (this._constraint) this._constraint.enableSpring(3, value); + } + } + + /** + * Gets whether the spring behavior is enabled for rotation around the X-axis. + * + * @type {boolean} + */ + get angularSpringX() { + return this._angularSpringX; + } + + /** + * Enables or disables the spring behavior for rotation around the Y-axis. + * + * @type {boolean} + */ + set angularSpringY(value) { + if (this._angularSpringY !== value) { + this._angularSpringY = value; + if (this._constraint) this._constraint.enableSpring(4, value); + } + } + + /** + * Gets whether the spring behavior is enabled for rotation around the Y-axis. + * + * @type {boolean} + */ + get angularSpringY() { + return this._angularSpringY; + } + + /** + * Enables or disables the spring behavior for rotation around the Z-axis. + * + * @type {boolean} + */ + set angularSpringZ(value) { + if (this._angularSpringZ !== value) { + this._angularSpringZ = value; + if (this._constraint) this._constraint.enableSpring(5, value); + } + } + + /** + * Gets whether the spring behavior is enabled for rotation around the Z-axis. + * + * @type {boolean} + */ + get angularSpringZ() { + return this._angularSpringZ; + } + + /** + * Sets the spring stiffness for rotation around the X-axis. + * + * @type {number} + */ + set angularStiffnessX(value) { + if (this._angularStiffnessX !== value) { + this._angularStiffnessX = value; + if (this._constraint) this._constraint.setStiffness(3, value); + } + } + + /** + * Gets the spring stiffness for rotation around the X-axis. + * + * @type {number} + */ + get angularStiffnessX() { + return this._angularStiffnessX; + } + + /** + * Sets the spring stiffness for rotation around the Y-axis. + * + * @type {number} + */ + set angularStiffnessY(value) { + if (this._angularStiffnessY !== value) { + this._angularStiffnessY = value; + if (this._constraint) this._constraint.setStiffness(4, value); + } + } + + /** + * Gets the spring stiffness for rotation around the Y-axis. + * + * @type {number} + */ + get angularStiffnessY() { + return this._angularStiffnessY; + } + + /** + * Sets the spring stiffness for rotation around the Z-axis. + * + * @type {number} + */ + set angularStiffnessZ(value) { + if (this._angularStiffnessZ !== value) { + this._angularStiffnessZ = value; + if (this._constraint) this._constraint.setStiffness(5, value); + } + } + + /** + * Gets the spring stiffness for rotation around the Z-axis. + * + * @type {number} + */ + get angularStiffnessZ() { + return this._angularStiffnessZ; + } + + /** + * Sets the breaking force threshold for the constraint. + * + * @type {number} + */ + set breakForce(force) { + if (this._constraint && this._breakForce !== force) { + this._constraint.setBreakingImpulseThreshold(force); + this._breakForce = force; + } + } + + /** + * Gets the breaking force threshold for the constraint. + * + * @type {number} + */ + get breakForce() { + return this._breakForce; + } + + /** + * Enables or disables collision between the constrained entities. + * + * @type {boolean} + */ + set enableCollision(enableCollision) { + this._destroyConstraint(); + this._enableCollision = enableCollision; + this._createConstraint(); + } + + /** + * Gets whether collision is enabled between the constrained entities. + * + * @type {boolean} + */ + get enableCollision() { + return this._enableCollision; + } + + /** + * Sets the first entity in the constraint. + * + * @type {Entity} + */ + set entityA(body) { + this._destroyConstraint(); + this._entityA = body; + this._createConstraint(); + } + + /** + * Gets the first entity in the constraint. + * + * @type {Entity} + */ + get entityA() { + return this._entityA; + } + + /** + * Sets the second entity in the constraint. + * + * @type {Entity} + */ + set entityB(body) { + this._destroyConstraint(); + this._entityB = body; + this._createConstraint(); + } + + /** + * Gets the second entity in the constraint. + * + * @type {Entity} + */ + get entityB() { + return this._entityB; + } + + /** + * Sets the linear damping for movement along the X-axis. + * + * @type {number} + */ + set linearDampingX(value) { + if (this._linearDampingX !== value) { + this._linearDampingX = value; + if (this._constraint) this._constraint.setDamping(0, value); + } + } + + /** + * Gets the linear damping for movement along the X-axis. + * + * @type {number} + */ + get linearDampingX() { + return this._linearDampingX; + } + + /** + * Sets the linear damping for movement along the Y-axis. + * + * @type {number} + */ + set linearDampingY(value) { + if (this._linearDampingY !== value) { + this._linearDampingY = value; + if (this._constraint) this._constraint.setDamping(1, value); + } + } + + /** + * Gets the linear damping for movement along the Y-axis. + * + * @type {number} + */ + get linearDampingY() { + return this._linearDampingY; + } + + /** + * Sets the linear damping for movement along the Z-axis. + * + * @type {number} + */ + set linearDampingZ(value) { + if (this._linearDampingZ !== value) { + this._linearDampingZ = value; + if (this._constraint) this._constraint.setDamping(2, value); + } + } + + /** + * Gets the linear damping for movement along the Z-axis. + * + * @type {number} + */ + get linearDampingZ() { + return this._linearDampingZ; + } + + /** + * Sets the linear equilibrium point for movement along the X-axis. + * + * @type {number} + */ + set linearEquilibriumX(value) { + if (this._linearEquilibriumX !== value) { + this._linearEquilibriumX = value; + if (this._constraint) this._constraint.setEquilibriumPoint(0, value); + } + } + + /** + * Gets the linear equilibrium point for movement along the X-axis. + * + * @type {number} + */ + get linearEquilibriumX() { + return this._linearEquilibriumX; + } + + /** + * Sets the linear equilibrium point for movement along the Y-axis. + * + * @type {number} + */ + set linearEquilibriumY(value) { + if (this._linearEquilibriumY !== value) { + this._linearEquilibriumY = value; + if (this._constraint) this._constraint.setEquilibriumPoint(1, value); + } + } + + /** + * Gets the linear equilibrium point for movement along the Y-axis. + * + * @type {number} + */ + get linearEquilibriumY() { + return this._linearEquilibriumY; + } + + /** + * Sets the linear equilibrium point for movement along the Z-axis. + * + * @type {number} + */ + set linearEquilibriumZ(value) { + if (this._linearEquilibriumZ !== value) { + this._linearEquilibriumZ = value; + if (this._constraint) this._constraint.setEquilibriumPoint(2, value); + } + } + + /** + * Gets the linear equilibrium point for movement along the Z-axis. + * + * @type {number} + */ + get linearEquilibriumZ() { + return this._linearEquilibriumZ; + } + + /** + * Sets the linear limits for movement along the X-axis. + * + * @type {Vec2} + */ set linearLimitsX(limits) { if (!this._linearLimitsX.equals(limits)) { this._linearLimitsX.copy(limits); @@ -216,54 +718,113 @@ class JointComponent extends Component { } } + /** + * Gets the linear limits for movement along the X-axis. + * + * @type {Vec2} + */ get linearLimitsX() { return this._linearLimitsX; } - set linearMotionX(value) { - if (this._linearMotionX !== value) { - this._linearMotionX = value; + /** + * Sets the linear limits for movement along the Y-axis. + * + * @type {Vec2} + */ + set linearLimitsY(limits) { + if (!this._linearLimitsY.equals(limits)) { + this._linearLimitsY.copy(limits); this._updateLinearLimits(); } } - get linearMotionX() { - return this._linearMotionX; + /** + * Gets the linear limits for movement along the Y-axis. + * + * @type {Vec2} + */ + get linearLimitsY() { + return this._linearLimitsY; } - set linearLimitsY(limits) { - if (!this._linearLimitsY.equals(limits)) { - this._linearLimitsY.copy(limits); + /** + * Sets the linear limits for movement along the Z-axis. + * + * @type {Vec2} + */ + set linearLimitsZ(limits) { + if (!this._linearLimitsZ.equals(limits)) { + this._linearLimitsZ.copy(limits); this._updateLinearLimits(); } } - get linearLimitsY() { - return this._linearLimitsY; + /** + * Gets the linear limits for movement along the Z-axis. + * + * @type {Vec2} + */ + get linearLimitsZ() { + return this._linearLimitsZ; } - set linearMotionY(value) { - if (this._linearMotionY !== value) { - this._linearMotionY = value; + /** + * Sets the type of motion allowed for movement along the X-axis. Can be: + * - MOTION_LOCKED: No movement allowed + * - MOTION_FREE: Unlimited movement allowed + * - MOTION_LIMITED: Movement limited by linearLimitsX + * + * @type {string} + */ + set linearMotionX(value) { + if (this._linearMotionX !== value) { + this._linearMotionX = value; this._updateLinearLimits(); } } - get linearMotionY() { - return this._linearMotionY; + /** + * Gets the type of motion allowed for movement along the X-axis. + * + * @type {string} + */ + get linearMotionX() { + return this._linearMotionX; } - set linearLimitsZ(limits) { - if (!this._linearLimitsZ.equals(limits)) { - this._linearLimitsZ.copy(limits); + /** + * Sets the type of motion allowed for movement along the Y-axis. Can be: + * - MOTION_LOCKED: No movement allowed + * - MOTION_FREE: Unlimited movement allowed + * - MOTION_LIMITED: Movement limited by linearLimitsY + * + * @type {string} + */ + set linearMotionY(value) { + if (this._linearMotionY !== value) { + this._linearMotionY = value; this._updateLinearLimits(); } } - get linearLimitsZ() { - return this._linearLimitsZ; + /** + * Gets the type of motion allowed for movement along the Y-axis. + * + * @type {string} + */ + get linearMotionY() { + return this._linearMotionY; } + /** + * Sets the type of motion allowed for movement along the Z-axis. Can be: + * - MOTION_LOCKED: No movement allowed + * - MOTION_FREE: Unlimited movement allowed + * - MOTION_LIMITED: Movement limited by linearLimitsZ + * + * @type {string} + */ set linearMotionZ(value) { if (this._linearMotionZ !== value) { this._linearMotionZ = value; @@ -271,10 +832,141 @@ class JointComponent extends Component { } } + /** + * Gets the type of motion allowed for movement along the Z-axis. + * + * @type {string} + */ get linearMotionZ() { return this._linearMotionZ; } + /** + * Enables or disables the spring behavior for movement along the X-axis. + * + * @type {boolean} + */ + set linearSpringX(value) { + if (this._linearSpringX !== value) { + this._linearSpringX = value; + if (this._constraint) this._constraint.enableSpring(0, value); + } + } + + /** + * Gets whether the spring behavior is enabled for movement along the X-axis. + * + * @type {boolean} + */ + get linearSpringX() { + return this._linearSpringX; + } + + /** + * Enables or disables the spring behavior for movement along the Y-axis. + * + * @type {boolean} + */ + set linearSpringY(value) { + if (this._linearSpringY !== value) { + this._linearSpringY = value; + if (this._constraint) this._constraint.enableSpring(1, value); + } + } + + /** + * Gets whether the spring behavior is enabled for movement along the Y-axis. + * + * @type {boolean} + */ + get linearSpringY() { + return this._linearSpringY; + } + + /** + * Enables or disables the spring behavior for movement along the Z-axis. + * + * @type {boolean} + */ + set linearSpringZ(value) { + if (this._linearSpringZ !== value) { + this._linearSpringZ = value; + if (this._constraint) this._constraint.enableSpring(2, value); + } + } + + /** + * Gets whether the spring behavior is enabled for movement along the Z-axis. + * + * @type {boolean} + */ + get linearSpringZ() { + return this._linearSpringZ; + } + + /** + * Sets the spring stiffness for movement along the X-axis. + * + * @type {number} + */ + set linearStiffnessX(value) { + if (this._linearStiffnessX !== value) { + this._linearStiffnessX = value; + if (this._constraint) this._constraint.setStiffness(0, value); + } + } + + /** + * Gets the spring stiffness for movement along the X-axis. + * + * @type {number} + */ + get linearStiffnessX() { + return this._linearStiffnessX; + } + + /** + * Sets the spring stiffness for movement along the Y-axis. + * + * @type {number} + */ + set linearStiffnessY(value) { + if (this._linearStiffnessY !== value) { + this._linearStiffnessY = value; + if (this._constraint) this._constraint.setStiffness(1, value); + } + } + + /** + * Gets the spring stiffness for movement along the Y-axis. + * + * @type {number} + */ + get linearStiffnessY() { + return this._linearStiffnessY; + } + + /** + * Sets the spring stiffness for movement along the Z-axis. + * + * @type {number} + */ + set linearStiffnessZ(value) { + if (this._linearStiffnessZ !== value) { + this._linearStiffnessZ = value; + if (this._constraint) this._constraint.setStiffness(2, value); + } + } + + /** + * Gets the spring stiffness for movement along the Z-axis. + * + * @type {number} + */ + get linearStiffnessZ() { + return this._linearStiffnessZ; + } + _convertTransform(pcTransform, ammoTransform) { const pos = pcTransform.getTranslation(); const rot = new Quat(); @@ -468,46 +1160,9 @@ class JointComponent extends Component { this._destroyConstraint(); } - _onSetEnabled(prop, old, value) { - } - _onBeforeRemove() { this.fire('remove'); } } -const functionMap = { - Damping: 'setDamping', - Equilibrium: 'setEquilibriumPoint', - Spring: 'enableSpring', - Stiffness: 'setStiffness' -}; - -// Define additional properties for each degree of freedom -['linear', 'angular'].forEach((type) => { - ['Damping', 'Equilibrium', 'Spring', 'Stiffness'].forEach((name) => { - ['X', 'Y', 'Z'].forEach((axis) => { - const prop = type + name + axis; - const propInternal = `_${prop}`; - - let index = (type === 'linear') ? 0 : 3; - if (axis === 'Y') index += 1; - if (axis === 'Z') index += 2; - - Object.defineProperty(JointComponent.prototype, prop, { - get: function () { - return this[propInternal]; - }, - - set: function (value) { - if (this[propInternal] !== value) { - this[propInternal] = value; - this._constraint[functionMap[name]](index, value); - } - } - }); - }); - }); -}); - export { JointComponent }; diff --git a/src/framework/components/joint/constants.js b/src/framework/components/joint/constants.js index c3fd0dee0df..5bc9be4423d 100644 --- a/src/framework/components/joint/constants.js +++ b/src/framework/components/joint/constants.js @@ -2,7 +2,6 @@ * Specified degree of freedom has free movement. * * @type {string} - * @ignore */ export const MOTION_FREE = 'free'; @@ -10,7 +9,6 @@ export const MOTION_FREE = 'free'; * Specified degree of freedom has limited movement. * * @type {string} - * @ignore */ export const MOTION_LIMITED = 'limited'; @@ -18,6 +16,5 @@ export const MOTION_LIMITED = 'limited'; * Specified degree of freedom is locked and allows no movement. * * @type {string} - * @ignore */ export const MOTION_LOCKED = 'locked'; diff --git a/src/framework/components/joint/system.js b/src/framework/components/joint/system.js index 5e8f0e7850e..61fc28c11bb 100644 --- a/src/framework/components/joint/system.js +++ b/src/framework/components/joint/system.js @@ -12,7 +12,7 @@ const _schema = ['enabled']; /** * Creates and manages physics joint components. * - * @ignore + * @beta */ class JointComponentSystem extends ComponentSystem { /** From 42586ffbf2405a498e26a46fdd281d569818efdb Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 23 Nov 2024 11:20:17 +0000 Subject: [PATCH 2/5] Make class fields private --- src/framework/components/joint/component.js | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/framework/components/joint/component.js b/src/framework/components/joint/component.js index 26b613289d8..f1dc9f5746b 100644 --- a/src/framework/components/joint/component.js +++ b/src/framework/components/joint/component.js @@ -43,58 +43,99 @@ const properties = [ * @beta */ class JointComponent extends Component { + /** @private */ _constraint = null; + /** @private */ _entityA = null; + /** @private */ _entityB = null; + /** @private */ _breakForce = 3.4e+38; + /** @private */ _enableCollision = true; // Linear X degree of freedom + /** @private */ _linearMotionX = MOTION_LOCKED; + /** @private */ _linearLimitsX = new Vec2(0, 0); + /** @private */ _linearSpringX = false; + /** @private */ _linearStiffnessX = 0; + /** @private */ _linearDampingX = 1; + /** @private */ _linearEquilibriumX = 0; // Linear Y degree of freedom + /** @private */ _linearMotionY = MOTION_LOCKED; + /** @private */ _linearLimitsY = new Vec2(0, 0); + /** @private */ _linearSpringY = false; + /** @private */ _linearStiffnessY = 0; + /** @private */ _linearDampingY = 1; + /** @private */ _linearEquilibriumY = 0; // Linear Z degree of freedom + /** @private */ _linearMotionZ = MOTION_LOCKED; + /** @private */ _linearLimitsZ = new Vec2(0, 0); + /** @private */ _linearSpringZ = false; + /** @private */ _linearStiffnessZ = 0; + /** @private */ _linearDampingZ = 1; + /** @private */ _linearEquilibriumZ = 0; // Angular X degree of freedom + /** @private */ _angularMotionX = MOTION_LOCKED; + /** @private */ _angularLimitsX = new Vec2(0, 0); + /** @private */ _angularSpringX = false; + /** @private */ _angularStiffnessX = 0; + /** @private */ _angularDampingX = 1; + /** @private */ _angularEquilibriumX = 0; // Angular Y degree of freedom + /** @private */ _angularMotionY = MOTION_LOCKED; + /** @private */ _angularLimitsY = new Vec2(0, 0); + /** @private */ _angularSpringY = false; + /** @private */ _angularStiffnessY = 0; + /** @private */ _angularDampingY = 1; + /** @private */ _angularEquilibriumY = 0; // Angular Z degree of freedom + /** @private */ _angularMotionZ = MOTION_LOCKED; + /** @private */ _angularLimitsZ = new Vec2(0, 0); + /** @private */ _angularSpringZ = false; + /** @private */ _angularEquilibriumZ = 0; + /** @private */ _angularDampingZ = 1; + /** @private */ _angularStiffnessZ = 0; /** From bc4d031f2c3dc693250c89c572d3adf449c32c04 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sun, 24 Nov 2024 10:20:35 +0000 Subject: [PATCH 3/5] Add rope bridge example --- .../examples/physics/rope-bridge.example.mjs | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 examples/src/examples/physics/rope-bridge.example.mjs diff --git a/examples/src/examples/physics/rope-bridge.example.mjs b/examples/src/examples/physics/rope-bridge.example.mjs new file mode 100644 index 00000000000..03e3e378b43 --- /dev/null +++ b/examples/src/examples/physics/rope-bridge.example.mjs @@ -0,0 +1,267 @@ +import { deviceType, rootPath, fileImport } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const { CameraControls } = await fileImport(`${rootPath}/static/scripts/camera-controls.mjs`); + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +pc.WasmModule.setConfig('Ammo', { + glueUrl: `${rootPath}/static/lib/ammo/ammo.wasm.js`, + wasmUrl: `${rootPath}/static/lib/ammo/ammo.wasm.wasm`, + fallbackUrl: `${rootPath}/static/lib/ammo/ammo.js` +}); +await new Promise((resolve) => { + pc.WasmModule.getInstance('Ammo', () => resolve()); +}); + +const gfxOptions = { + deviceTypes: [deviceType], + glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, + twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.keyboard = new pc.Keyboard(document.body); + +createOptions.componentSystems = [ + pc.CameraComponentSystem, + pc.CollisionComponentSystem, + pc.JointComponentSystem, + pc.LightComponentSystem, + pc.RenderComponentSystem, + pc.RigidBodyComponentSystem, + pc.ScriptComponentSystem +]; +createOptions.resourceHandlers = [ + pc.ScriptHandler +]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +// Start the app and create the scene +app.start(); + +// Create materials +const woodMaterial = new pc.StandardMaterial(); +woodMaterial.diffuse = new pc.Color(0.7, 0.5, 0.3); +woodMaterial.update(); + +const anchorMaterial = new pc.StandardMaterial(); +anchorMaterial.diffuse = new pc.Color(0.3, 0.3, 0.3); +anchorMaterial.update(); + +// Create the camera +const camera = new pc.Entity('Camera'); +camera.addComponent('camera', { + clearColor: new pc.Color(0.5, 0.5, 0.8) +}); +camera.addComponent('script'); +camera.setLocalPosition(0, 7, 10); +app.root.addChild(camera); + +camera.script.create(CameraControls, { + attributes: { + focusPoint: new pc.Vec3(0, 3, 0), + sceneSize: 10 + } +}); + +// Create the light +const light = new pc.Entity('Light'); +light.addComponent('light', { + type: 'directional', + castShadows: true, + shadowBias: 0.2, + shadowDistance: 40, + normalOffsetBias: 0.05, + shadowResolution: 2048 +}); +light.setEulerAngles(45, 30, 0); +app.root.addChild(light); + +function createGround() { + const ground = new pc.Entity(); + ground.addComponent('render', { type: 'plane' }); + ground.addComponent('rigidbody', { + type: 'static', + restitution: 0.5 + }); + ground.addComponent('collision', { + type: 'box', + halfExtents: new pc.Vec3(20, 0.5, 10), + linearOffset: new pc.Vec3(0, -0.5, 0) + }); + ground.setLocalScale(40, 1, 20); + app.root.addChild(ground); +} + +// Create a bridge with physics joints +function createBridge(startPos, endPos, segments) { + const plankWidth = 0.5; // Width along X axis (spacing between planks) + const plankHeight = 0.1; // Height along Y axis + const plankLength = 2; // Length along Z axis (across the bridge) + + const plankPos = startPos.clone(); + plankPos.x += plankWidth / 2; + + // Create bridge planks with minimal spacing + const planks = []; + for (let i = 0; i < segments; i++) { + // Create the physics plank with no scaling + const plank = new pc.Entity(); + plank.addComponent('rigidbody', { + type: 'dynamic', + mass: 0.5, + friction: 0.6, + restitution: 0.1 + }); + plank.addComponent('collision', { + type: 'box', + halfExtents: new pc.Vec3(plankWidth/2, plankHeight/2, plankLength/2) + }); + + // Position the plank + plank.setLocalPosition(plankPos); + plankPos.x += plankWidth; + + // Create a child entity for the visual representation + const visualPlank = new pc.Entity('visual'); + visualPlank.addComponent('render', { + type: 'box', + material: woodMaterial + }); + visualPlank.setLocalScale(plankWidth, plankHeight, plankLength); + plank.addChild(visualPlank); + + app.root.addChild(plank); + planks.push(plank); + } + + // Connect planks with joints + const jointPos = startPos.clone(); + jointPos.x += plankWidth; + + for (let i = 0; i < segments - 1; i++) { + const joint = new pc.Entity(); + + // Position the joint at the center of plankA + joint.setLocalPosition(jointPos); + jointPos.x += plankWidth; + + joint.addComponent('joint', { + entityA: planks[i], + entityB: planks[i + 1], + enableCollision: false + }); + app.root.addChild(joint); + } + + // Create fixed anchor points in space + function createFixedPoint(pos, plank) { + const joint = new pc.Entity(); + joint.setLocalPosition(pos); + + joint.addComponent('joint', { + entityA: plank, // Only connect to the plank + entityB: null, // No second entity needed for fixed point + enableCollision: false, + angularMotionZ: pc.MOTION_FREE + }); + app.root.addChild(joint); + } + + // Create fixed points at start and end + createFixedPoint(startPos, planks[0]); + createFixedPoint(endPos, planks[planks.length - 1]); +} + +// Create towers at the ends of the bridge +function createTower(position) { + const towerWidth = 2; + const towerHeight = 6; + const towerDepth = 2; + + const tower = new pc.Entity('Tower'); + tower.addComponent('rigidbody', { + type: 'static' + }); + tower.addComponent('collision', { + type: 'box', + halfExtents: new pc.Vec3(towerWidth/2, towerHeight/2, towerDepth/2) + }); + + const visualTower = new pc.Entity('visual'); + visualTower.addComponent('render', { + type: 'box', + material: anchorMaterial + }); + visualTower.setLocalScale(towerWidth, towerHeight, towerDepth); + tower.addChild(visualTower); + + const towerPos = new pc.Vec3(position.x, position.y - 3, position.z); + tower.setLocalPosition(towerPos); + app.root.addChild(tower); +} + +// Create bridge and towers +const towerWidth = 2; + +// Calculate bridge connection points at tower sides and plank edges +const bridgeStart = new pc.Vec3(-5, 5, 0); // Right side of left tower + half plank +const bridgeEnd = new pc.Vec3(5, 5, 0); // Left side of right tower - half plank + +createGround(); +createTower(new pc.Vec3(-5 - towerWidth/2, 5, 0)); +createTower(new pc.Vec3(5 + towerWidth/2, 5, 0)); +createBridge(bridgeStart, bridgeEnd, 20); + +// Create a test sphere to drop on the bridge +function createTestSphere(position) { + const sphere = new pc.Entity(); + sphere.addComponent('render', { + type: 'sphere' + }); + sphere.addComponent('rigidbody', { + type: 'dynamic', + mass: 5, + friction: 0.5, + restitution: 0.3 + }); + sphere.addComponent('collision', { + type: 'sphere', + radius: 0.5 + }); + sphere.setLocalPosition(position); + app.root.addChild(sphere); + return sphere; +} + +// Add a sphere that can be dropped with spacebar +let testSphere = null; +app.keyboard.on('keydown', (event) => { + if (event.key === pc.KEY_SPACE) { + if (testSphere) { + testSphere.destroy(); + } + testSphere = createTestSphere(new pc.Vec3(0, 10, 0)); + } +}); + +export { app }; \ No newline at end of file From 51599d5136ae448f3e325443dc4cd11f455c782e Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sun, 24 Nov 2024 10:30:53 +0000 Subject: [PATCH 4/5] Lint fixes --- src/framework/components/joint/component.js | 42 +++++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/framework/components/joint/component.js b/src/framework/components/joint/component.js index f1dc9f5746b..1453a0997cd 100644 --- a/src/framework/components/joint/component.js +++ b/src/framework/components/joint/component.js @@ -28,113 +28,147 @@ const properties = [ ]; /** - * The JointComponent adds a physics joint constraint between two rigid body components. + * The JointComponent adds a physics joint constraint between two rigid body components. * A joint connects two rigid bodies and restricts their relative movement in various ways. * It supports both linear and angular constraints along all three axes, with options for: - * + * * - Locked motion (no movement) * - Free motion (unrestricted movement) * - Limited motion (movement within specified limits) * - Spring behavior (with configurable stiffness, damping, and equilibrium points) - * + * * This can be used to create a variety of joint types like hinges, sliders, ball-and-socket joints, etc. * Each degree of freedom (linear and angular) for each axis (X, Y, Z) can be configured independently. - * + * * @beta */ class JointComponent extends Component { /** @private */ _constraint = null; + /** @private */ _entityA = null; + /** @private */ _entityB = null; + /** @private */ _breakForce = 3.4e+38; + /** @private */ _enableCollision = true; // Linear X degree of freedom /** @private */ _linearMotionX = MOTION_LOCKED; + /** @private */ _linearLimitsX = new Vec2(0, 0); + /** @private */ _linearSpringX = false; + /** @private */ _linearStiffnessX = 0; + /** @private */ _linearDampingX = 1; + /** @private */ _linearEquilibriumX = 0; // Linear Y degree of freedom /** @private */ _linearMotionY = MOTION_LOCKED; + /** @private */ _linearLimitsY = new Vec2(0, 0); + /** @private */ _linearSpringY = false; + /** @private */ _linearStiffnessY = 0; + /** @private */ _linearDampingY = 1; + /** @private */ _linearEquilibriumY = 0; // Linear Z degree of freedom /** @private */ _linearMotionZ = MOTION_LOCKED; + /** @private */ _linearLimitsZ = new Vec2(0, 0); + /** @private */ _linearSpringZ = false; + /** @private */ _linearStiffnessZ = 0; + /** @private */ _linearDampingZ = 1; + /** @private */ _linearEquilibriumZ = 0; // Angular X degree of freedom /** @private */ _angularMotionX = MOTION_LOCKED; + /** @private */ _angularLimitsX = new Vec2(0, 0); + /** @private */ _angularSpringX = false; + /** @private */ _angularStiffnessX = 0; + /** @private */ _angularDampingX = 1; + /** @private */ _angularEquilibriumX = 0; // Angular Y degree of freedom /** @private */ _angularMotionY = MOTION_LOCKED; + /** @private */ _angularLimitsY = new Vec2(0, 0); + /** @private */ _angularSpringY = false; + /** @private */ _angularStiffnessY = 0; + /** @private */ _angularDampingY = 1; + /** @private */ _angularEquilibriumY = 0; // Angular Z degree of freedom /** @private */ _angularMotionZ = MOTION_LOCKED; + /** @private */ _angularLimitsZ = new Vec2(0, 0); + /** @private */ _angularSpringZ = false; + /** @private */ _angularEquilibriumZ = 0; + /** @private */ _angularDampingZ = 1; + /** @private */ _angularStiffnessZ = 0; From 602deef6fffb80f016b696d8efc16938ef1028f1 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Tue, 26 Nov 2024 11:26:50 +0000 Subject: [PATCH 5/5] Lint fixes --- .../examples/physics/rope-bridge.example.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/src/examples/physics/rope-bridge.example.mjs b/examples/src/examples/physics/rope-bridge.example.mjs index 03e3e378b43..80b6185dcb7 100644 --- a/examples/src/examples/physics/rope-bridge.example.mjs +++ b/examples/src/examples/physics/rope-bridge.example.mjs @@ -117,7 +117,7 @@ function createBridge(startPos, endPos, segments) { const plankWidth = 0.5; // Width along X axis (spacing between planks) const plankHeight = 0.1; // Height along Y axis const plankLength = 2; // Length along Z axis (across the bridge) - + const plankPos = startPos.clone(); plankPos.x += plankWidth / 2; @@ -134,13 +134,13 @@ function createBridge(startPos, endPos, segments) { }); plank.addComponent('collision', { type: 'box', - halfExtents: new pc.Vec3(plankWidth/2, plankHeight/2, plankLength/2) + halfExtents: new pc.Vec3(plankWidth / 2, plankHeight / 2, plankLength / 2) }); // Position the plank plank.setLocalPosition(plankPos); plankPos.x += plankWidth; - + // Create a child entity for the visual representation const visualPlank = new pc.Entity('visual'); visualPlank.addComponent('render', { @@ -149,7 +149,7 @@ function createBridge(startPos, endPos, segments) { }); visualPlank.setLocalScale(plankWidth, plankHeight, plankLength); plank.addChild(visualPlank); - + app.root.addChild(plank); planks.push(plank); } @@ -160,7 +160,7 @@ function createBridge(startPos, endPos, segments) { for (let i = 0; i < segments - 1; i++) { const joint = new pc.Entity(); - + // Position the joint at the center of plankA joint.setLocalPosition(jointPos); jointPos.x += plankWidth; @@ -204,7 +204,7 @@ function createTower(position) { }); tower.addComponent('collision', { type: 'box', - halfExtents: new pc.Vec3(towerWidth/2, towerHeight/2, towerDepth/2) + halfExtents: new pc.Vec3(towerWidth / 2, towerHeight / 2, towerDepth / 2) }); const visualTower = new pc.Entity('visual'); @@ -228,8 +228,8 @@ const bridgeStart = new pc.Vec3(-5, 5, 0); // Right side of left tower + half p const bridgeEnd = new pc.Vec3(5, 5, 0); // Left side of right tower - half plank createGround(); -createTower(new pc.Vec3(-5 - towerWidth/2, 5, 0)); -createTower(new pc.Vec3(5 + towerWidth/2, 5, 0)); +createTower(new pc.Vec3(-5 - towerWidth / 2, 5, 0)); +createTower(new pc.Vec3(5 + towerWidth / 2, 5, 0)); createBridge(bridgeStart, bridgeEnd, 20); // Create a test sphere to drop on the bridge @@ -264,4 +264,4 @@ app.keyboard.on('keydown', (event) => { } }); -export { app }; \ No newline at end of file +export { app };