UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

448 lines (445 loc) 12.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_LOCKED, MOTION_LIMITED, MOTION_FREE } 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 = 3.4e+38; 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 };