UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

430 lines (429 loc) 11.7 kB
import { math } from "../../../core/math/math.js"; import { Mat4 } from "../../../core/math/mat4.js"; import { Quat } from "../../../core/math/quat.js"; import { Vec2 } from "../../../core/math/vec2.js"; import { Component } from "../component.js"; import { MOTION_FREE, MOTION_LIMITED, MOTION_LOCKED } from "./constants.js"; const properties = [ "angularDampingX", "angularDampingY", "angularDampingZ", "angularEquilibriumX", "angularEquilibriumY", "angularEquilibriumZ", "angularLimitsX", "angularLimitsY", "angularLimitsZ", "angularMotionX", "angularMotionY", "angularMotionZ", "angularSpringX", "angularSpringY", "angularSpringZ", "angularStiffnessX", "angularStiffnessY", "angularStiffnessZ", "breakForce", "enableCollision", "enabled", "entityA", "entityB", "linearDampingX", "linearDampingY", "linearDampingZ", "linearEquilibriumX", "linearEquilibriumY", "linearEquilibriumZ", "linearLimitsX", "linearLimitsY", "linearLimitsZ", "linearMotionX", "linearMotionY", "linearMotionZ", "linearSpringX", "linearSpringY", "linearSpringZ", "linearStiffnessX", "linearStiffnessY", "linearStiffnessZ" ]; class JointComponent extends Component { constructor(system, entity) { super(system, entity); this._constraint = null; this._entityA = null; this._entityB = null; this._breakForce = 34e37; this._enableCollision = true; this._linearMotionX = MOTION_LOCKED; this._linearLimitsX = new Vec2(0, 0); this._linearSpringX = false; this._linearStiffnessX = 0; this._linearDampingX = 1; this._linearEquilibriumX = 0; this._linearMotionY = MOTION_LOCKED; this._linearLimitsY = new Vec2(0, 0); this._linearSpringY = false; this._linearStiffnessY = 0; this._linearDampingY = 1; this._linearEquilibriumY = 0; this._linearMotionZ = MOTION_LOCKED; this._linearLimitsZ = new Vec2(0, 0); this._linearSpringZ = false; this._linearStiffnessZ = 0; this._linearDampingZ = 1; this._linearEquilibriumZ = 0; this._angularMotionX = MOTION_LOCKED; this._angularLimitsX = new Vec2(0, 0); this._angularSpringX = false; this._angularStiffnessX = 0; this._angularDampingX = 1; this._angularEquilibriumX = 0; this._angularMotionY = MOTION_LOCKED; this._angularLimitsY = new Vec2(0, 0); this._angularSpringY = false; this._angularStiffnessY = 0; this._angularDampingY = 1; this._angularEquilibriumY = 0; 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); } set entityA(body) { this._destroyConstraint(); this._entityA = body; this._createConstraint(); } get entityA() { return this._entityA; } set entityB(body) { this._destroyConstraint(); this._entityB = body; this._createConstraint(); } get entityB() { return this._entityB; } set breakForce(force) { if (this._constraint && this._breakForce !== force) { this._constraint.setBreakingImpulseThreshold(force); this._breakForce = force; } } get breakForce() { return this._breakForce; } set enableCollision(enableCollision) { this._destroyConstraint(); this._enableCollision = enableCollision; this._createConstraint(); } get enableCollision() { return this._enableCollision; } set angularLimitsX(limits) { if (!this._angularLimitsX.equals(limits)) { this._angularLimitsX.copy(limits); this._updateAngularLimits(); } } get angularLimitsX() { return this._angularLimitsX; } set angularMotionX(value) { if (this._angularMotionX !== value) { this._angularMotionX = value; this._updateAngularLimits(); } } get angularMotionX() { return this._angularMotionX; } set angularLimitsY(limits) { if (!this._angularLimitsY.equals(limits)) { this._angularLimitsY.copy(limits); this._updateAngularLimits(); } } get angularLimitsY() { return this._angularLimitsY; } set angularMotionY(value) { if (this._angularMotionY !== value) { this._angularMotionY = value; this._updateAngularLimits(); } } get angularMotionY() { return this._angularMotionY; } set angularLimitsZ(limits) { if (!this._angularLimitsZ.equals(limits)) { this._angularLimitsZ.copy(limits); this._updateAngularLimits(); } } get angularLimitsZ() { return this._angularLimitsZ; } set angularMotionZ(value) { if (this._angularMotionZ !== value) { this._angularMotionZ = value; this._updateAngularLimits(); } } get angularMotionZ() { return this._angularMotionZ; } set linearLimitsX(limits) { if (!this._linearLimitsX.equals(limits)) { this._linearLimitsX.copy(limits); this._updateLinearLimits(); } } get linearLimitsX() { return this._linearLimitsX; } set linearMotionX(value) { if (this._linearMotionX !== value) { this._linearMotionX = value; this._updateLinearLimits(); } } get linearMotionX() { return this._linearMotionX; } set linearLimitsY(limits) { if (!this._linearLimitsY.equals(limits)) { this._linearLimitsY.copy(limits); this._updateLinearLimits(); } } get linearLimitsY() { return this._linearLimitsY; } set linearMotionY(value) { if (this._linearMotionY !== value) { this._linearMotionY = value; this._updateLinearLimits(); } } get linearMotionY() { return this._linearMotionY; } set linearLimitsZ(limits) { if (!this._linearLimitsZ.equals(limits)) { this._linearLimitsZ.copy(limits); this._updateLinearLimits(); } } get linearLimitsZ() { return this._linearLimitsZ; } set linearMotionZ(value) { if (this._linearMotionZ !== value) { this._linearMotionZ = value; this._updateLinearLimits(); } } get linearMotionZ() { return this._linearMotionZ; } _convertTransform(pcTransform, ammoTransform) { const pos = pcTransform.getTranslation(); const rot = new Quat(); rot.setFromMat4(pcTransform); const ammoVec = new Ammo.btVector3(pos.x, pos.y, pos.z); const ammoQuat = new Ammo.btQuaternion(rot.x, rot.y, rot.z, rot.w); ammoTransform.setOrigin(ammoVec); ammoTransform.setRotation(ammoQuat); Ammo.destroy(ammoVec); Ammo.destroy(ammoQuat); } _updateAngularLimits() { const constraint = this._constraint; if (constraint) { let lx, ly, lz, ux, uy, uz; if (this._angularMotionX === MOTION_LIMITED) { lx = this._angularLimitsX.x * math.DEG_TO_RAD; ux = this._angularLimitsX.y * math.DEG_TO_RAD; } else if (this._angularMotionX === MOTION_FREE) { lx = 1; ux = 0; } else { lx = ux = 0; } if (this._angularMotionY === MOTION_LIMITED) { ly = this._angularLimitsY.x * math.DEG_TO_RAD; uy = this._angularLimitsY.y * math.DEG_TO_RAD; } else if (this._angularMotionY === MOTION_FREE) { ly = 1; uy = 0; } else { ly = uy = 0; } if (this._angularMotionZ === MOTION_LIMITED) { lz = this._angularLimitsZ.x * math.DEG_TO_RAD; uz = this._angularLimitsZ.y * math.DEG_TO_RAD; } else if (this._angularMotionZ === MOTION_FREE) { lz = 1; uz = 0; } else { lz = uz = 0; } const limits = new Ammo.btVector3(lx, ly, lz); constraint.setAngularLowerLimit(limits); limits.setValue(ux, uy, uz); constraint.setAngularUpperLimit(limits); Ammo.destroy(limits); } } _updateLinearLimits() { const constraint = this._constraint; if (constraint) { let lx, ly, lz, ux, uy, uz; if (this._linearMotionX === MOTION_LIMITED) { lx = this._linearLimitsX.x; ux = this._linearLimitsX.y; } else if (this._linearMotionX === MOTION_FREE) { lx = 1; ux = 0; } else { lx = ux = 0; } if (this._linearMotionY === MOTION_LIMITED) { ly = this._linearLimitsY.x; uy = this._linearLimitsY.y; } else if (this._linearMotionY === MOTION_FREE) { ly = 1; uy = 0; } else { ly = uy = 0; } if (this._linearMotionZ === MOTION_LIMITED) { lz = this._linearLimitsZ.x; uz = this._linearLimitsZ.y; } else if (this._linearMotionZ === MOTION_FREE) { lz = 1; uz = 0; } else { lz = uz = 0; } const limits = new Ammo.btVector3(lx, ly, lz); constraint.setLinearLowerLimit(limits); limits.setValue(ux, uy, uz); constraint.setLinearUpperLimit(limits); Ammo.destroy(limits); } } _createConstraint() { if (this._entityA && this._entityA.rigidbody) { this._destroyConstraint(); const mat = new Mat4(); const bodyA = this._entityA.rigidbody.body; bodyA.activate(); const jointWtm = this.entity.getWorldTransform(); const entityAWtm = this._entityA.getWorldTransform(); const invEntityAWtm = entityAWtm.clone().invert(); mat.mul2(invEntityAWtm, jointWtm); const frameA = new Ammo.btTransform(); this._convertTransform(mat, frameA); if (this._entityB && this._entityB.rigidbody) { const bodyB = this._entityB.rigidbody.body; bodyB.activate(); const entityBWtm = this._entityB.getWorldTransform(); const invEntityBWtm = entityBWtm.clone().invert(); mat.mul2(invEntityBWtm, jointWtm); const frameB = new Ammo.btTransform(); this._convertTransform(mat, frameB); this._constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA, bodyB, frameA, frameB, !this._enableCollision); Ammo.destroy(frameB); } else { this._constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA, frameA, !this._enableCollision); } Ammo.destroy(frameA); const axis = ["X", "Y", "Z", "X", "Y", "Z"]; for (let i = 0; i < 6; i++) { const type = i < 3 ? "_linear" : "_angular"; this._constraint.enableSpring(i, this[`${type}Spring${axis[i]}`]); this._constraint.setDamping(i, this[`${type}Damping${axis[i]}`]); this._constraint.setEquilibriumPoint(i, this[`${type}Equilibrium${axis[i]}`]); this._constraint.setStiffness(i, this[`${type}Stiffness${axis[i]}`]); } this._constraint.setBreakingImpulseThreshold(this._breakForce); this._updateLinearLimits(); this._updateAngularLimits(); const app = this.system.app; const dynamicsWorld = app.systems.rigidbody.dynamicsWorld; dynamicsWorld.addConstraint(this._constraint, !this._enableCollision); } } _destroyConstraint() { if (this._constraint) { const app = this.system.app; const dynamicsWorld = app.systems.rigidbody.dynamicsWorld; dynamicsWorld.removeConstraint(this._constraint); Ammo.destroy(this._constraint); this._constraint = null; } } initFromData(data) { for (const prop of properties) { if (data.hasOwnProperty(prop)) { if (data[prop] instanceof Vec2) { this[`_${prop}`].copy(data[prop]); } else { this[`_${prop}`] = data[prop]; } } } this._createConstraint(); } onEnable() { this._createConstraint(); } onDisable() { this._destroyConstraint(); } _onSetEnabled(prop, old, value) { } _onBeforeRemove() { this.fire("remove"); } } const functionMap = { Damping: "setDamping", Equilibrium: "setEquilibriumPoint", Spring: "enableSpring", Stiffness: "setStiffness" }; ["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 };