UNPKG

@rtsdk/lance-topia

Version:

A Node.js based real-time multiplayer game server

214 lines (181 loc) 8.76 kB
import GameObject from './GameObject'; import BaseTypes from './BaseTypes'; import ThreeVector from './ThreeVector'; import Quaternion from './Quaternion'; /** * The PhysicalObject3D is the base class for physical game objects */ class PhysicalObject3D extends GameObject { /** * The netScheme is a dictionary of attributes in this game * object. The attributes listed in the netScheme are those exact * attributes which will be serialized and sent from the server * to each client on every server update. * The netScheme member is implemented as a getter. * * You may choose not to implement this method, in which * case your object only transmits the default attributes * which are already part of {@link PhysicalObject3D}. * But if you choose to add more attributes, make sure * the return value includes the netScheme of the super class. * * @memberof PhysicalObject3D * @member {Object} netScheme * @example * static get netScheme() { * return Object.assign({ * mojo: { type: BaseTypes.TYPES.UINT8 }, * }, super.netScheme); * } */ static get netScheme() { return Object.assign({ position: { type: BaseTypes.TYPES.CLASSINSTANCE }, quaternion: { type: BaseTypes.TYPES.CLASSINSTANCE }, velocity: { type: BaseTypes.TYPES.CLASSINSTANCE }, angularVelocity: { type: BaseTypes.TYPES.CLASSINSTANCE } }, super.netScheme); } /** * Creates an instance of a physical object. * Override to provide starting values for position, velocity, quaternion and angular velocity. * NOTE: all subclasses of this class must comply with this constructor signature. * This is required because the engine will create temporary instances when * syncs arrive on the clients. * @param {GameEngine} gameEngine - the gameEngine this object will be used in * @param {Object} options - options for the new object. See {@link GameObject} * @param {Object} props - properties to be set in the new object * @param {ThreeVector} props.position - position vector * @param {ThreeVector} props.velocity - velocity vector * @param {Quaternion} props.quaternion - orientation quaternion * @param {ThreeVector} props.angularVelocity - 3-vector representation of angular velocity */ constructor(gameEngine, options, props) { super(gameEngine, options, props); this.bendingIncrements = 0; // set default position, velocity and quaternion this.position = new ThreeVector(0, 0, 0); this.velocity = new ThreeVector(0, 0, 0); this.quaternion = new Quaternion(1, 0, 0, 0); this.angularVelocity = new ThreeVector(0, 0, 0); // use values if provided props = props || {}; if (props.position) this.position.copy(props.position); if (props.velocity) this.velocity.copy(props.velocity); if (props.quaternion) this.quaternion.copy(props.quaternion); if (props.angularVelocity) this.angularVelocity.copy(props.angularVelocity); this.class = PhysicalObject3D; } /** * Formatted textual description of the dynamic object. * The output of this method is used to describe each instance in the traces, * which significantly helps in debugging. * * @return {String} description - a string describing the PhysicalObject3D */ toString() { let p = this.position.toString(); let v = this.velocity.toString(); let q = this.quaternion.toString(); let a = this.angularVelocity.toString(); return `phyObj[${this.id}] player${this.playerId} Pos${p} Vel${v} Dir${q} AVel${a}`; } // display object's physical attributes as a string // for debugging purposes mostly bendingToString() { if (this.bendingOptions) return `bend=${this.bendingOptions.percent} deltaPos=${this.bendingPositionDelta} deltaVel=${this.bendingVelocityDelta} deltaQuat=${this.bendingQuaternionDelta}`; return 'no bending'; } // derive and save the bending increment parameters: // - bendingPositionDelta // - bendingAVDelta // - bendingQuaternionDelta // these can later be used to "bend" incrementally from the state described // by "original" to the state described by "self" bendToCurrent(original, percent, worldSettings, isLocal, increments) { let bending = { increments, percent }; // if the object has defined a bending multiples for this object, use them let positionBending = Object.assign({}, bending, this.bending.position); let velocityBending = Object.assign({}, bending, this.bending.velocity); // check for local object overrides to bendingTarget if (isLocal) { Object.assign(positionBending, this.bending.positionLocal); Object.assign(velocityBending, this.bending.velocityLocal); } // get the incremental delta position & velocity this.incrementScale = percent / increments; this.bendingPositionDelta = original.position.getBendingDelta(this.position, positionBending); this.bendingVelocityDelta = original.velocity.getBendingDelta(this.velocity, velocityBending); this.bendingAVDelta = new ThreeVector(0, 0, 0); // get the incremental quaternion rotation this.bendingQuaternionDelta = (new Quaternion()).copy(original.quaternion).conjugate(); this.bendingQuaternionDelta.multiply(this.quaternion); let axisAngle = this.bendingQuaternionDelta.toAxisAngle(); axisAngle.angle *= this.incrementScale; this.bendingQuaternionDelta.setFromAxisAngle(axisAngle.axis, axisAngle.angle); this.bendingTarget = (new this.constructor()); this.bendingTarget.syncTo(this); this.position.copy(original.position); this.quaternion.copy(original.quaternion); this.angularVelocity.copy(original.angularVelocity); this.bendingIncrements = increments; this.bendingOptions = bending; this.refreshToPhysics(); } syncTo(other, options) { super.syncTo(other); this.position.copy(other.position); this.quaternion.copy(other.quaternion); this.angularVelocity.copy(other.angularVelocity); if (!options || !options.keepVelocity) { this.velocity.copy(other.velocity); } if (this.physicsObj) this.refreshToPhysics(); } // update position, quaternion, and velocity from new physical state. refreshFromPhysics() { this.position.copy(this.physicsObj.position); this.quaternion.copy(this.physicsObj.quaternion); this.velocity.copy(this.physicsObj.velocity); this.angularVelocity.copy(this.physicsObj.angularVelocity); } // update position, quaternion, and velocity from new game state. refreshToPhysics() { this.physicsObj.position.copy(this.position); this.physicsObj.quaternion.copy(this.quaternion); this.physicsObj.velocity.copy(this.velocity); this.physicsObj.angularVelocity.copy(this.angularVelocity); } // apply one increment of bending applyIncrementalBending(stepDesc) { if (this.bendingIncrements === 0) return; if (stepDesc && stepDesc.dt) { const timeFactor = stepDesc.dt / (1000 / 60); // TODO: use clone() below. it's cleaner const posDelta = (new ThreeVector()).copy(this.bendingPositionDelta).multiplyScalar(timeFactor); const avDelta = (new ThreeVector()).copy(this.bendingAVDelta).multiplyScalar(timeFactor); this.position.add(posDelta); this.angularVelocity.add(avDelta); // one approach to orientation bending is slerp: this.quaternion.slerp(this.bendingTarget.quaternion, this.incrementScale * timeFactor * 0.8); } else { this.position.add(this.bendingPositionDelta); this.angularVelocity.add(this.bendingAVDelta); this.quaternion.slerp(this.bendingTarget.quaternion, this.incrementScale); } // alternative: fixed delta-quaternion correction // TODO: adjust quaternion bending to dt timefactor precision // this.quaternion.multiply(this.bendingQuaternionDelta); this.bendingIncrements--; } // interpolate implementation interpolate(nextObj, percent) { // slerp to target position this.position.lerp(nextObj.position, percent); this.quaternion.slerp(nextObj.quaternion, percent); } } export default PhysicalObject3D;