mirror of
https://github.com/overte-org/overte.git
synced 2025-04-26 07:36:36 +02:00
268 lines
12 KiB
JavaScript
268 lines
12 KiB
JavaScript
/* eslint-env commonjs */
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// helper module that performs the avatar movement update calculations
|
|
|
|
module.exports = AvatarUpdater;
|
|
|
|
var _utils = require('./modules/_utils.js'),
|
|
assert = _utils.assert;
|
|
|
|
var movementUtils = require('./modules/movement-utils.js');
|
|
|
|
function AvatarUpdater(options) {
|
|
options = options || {};
|
|
assert(function assertion() {
|
|
return typeof options.getCameraMovementSettings === 'function' &&
|
|
typeof options.getMovementState === 'function' &&
|
|
options.globalState;
|
|
});
|
|
|
|
var DEFAULT_MOTOR_TIMESCALE = 1e6; // a large value that matches Interface's default
|
|
var EASED_MOTOR_TIMESCALE = 0.01; // a small value to make Interface quickly apply MyAvatar.motorVelocity
|
|
var EASED_MOTOR_THRESHOLD = 0.1; // above this speed (m/s) EASED_MOTOR_TIMESCALE is used
|
|
var ACCELERATION_MULTIPLIERS = { translation: 1, rotation: 1, zoom: 1 };
|
|
var STAYGROUNDED_PITCH_THRESHOLD = 45.0; // degrees; ground level is maintained when pitch is within this threshold
|
|
var MIN_DELTA_TIME = 0.0001; // to avoid math overflow, never consider dt less than this value
|
|
var DEG_TO_RAD = Math.PI / 180.0;
|
|
update.frameCount = 0;
|
|
update.endTime = update.windowStartTime = _utils.getRuntimeSeconds();
|
|
update.windowFrame = update.windowStartFrame= 0;
|
|
|
|
this.update = update;
|
|
this.options = options;
|
|
this._resetMyAvatarMotor = _resetMyAvatarMotor;
|
|
this._applyDirectPitchYaw = _applyDirectPitchYaw;
|
|
|
|
var globalState = options.globalState;
|
|
var getCameraMovementSettings = options.getCameraMovementSettings;
|
|
var getMovementState = options.getMovementState;
|
|
var _debugChannel = options.debugChannel;
|
|
function update(dt) {
|
|
update.frameCount++;
|
|
var startTime = _utils.getRuntimeSeconds();
|
|
var settings = getCameraMovementSettings(),
|
|
EPSILON = settings.epsilon;
|
|
|
|
var independentCamera = Camera.mode === 'independent',
|
|
headPitch = MyAvatar.headPitch;
|
|
|
|
var actualDeltaTime = startTime - update.endTime,
|
|
practicalDeltaTime = Math.max(MIN_DELTA_TIME, actualDeltaTime),
|
|
deltaTime;
|
|
|
|
if (settings.useConstantDeltaTime) {
|
|
deltaTime = settings.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ?
|
|
(1 / settings.fps) : (1 / 90);
|
|
} else if (settings.threadMode === movementUtils.CameraControls.SCRIPT_UPDATE) {
|
|
deltaTime = dt;
|
|
} else {
|
|
deltaTime = practicalDeltaTime;
|
|
}
|
|
|
|
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
|
currentOrientation = independentCamera ? Camera.orientation : MyAvatar[orientationProperty],
|
|
currentPosition = MyAvatar.position;
|
|
|
|
var previousValues = globalState.previousValues,
|
|
pendingChanges = globalState.pendingChanges,
|
|
currentVelocities = globalState.currentVelocities;
|
|
|
|
var movementState = getMovementState({ update: deltaTime }),
|
|
targetState = movementUtils.applyEasing(deltaTime, 'easeIn', settings, movementState, ACCELERATION_MULTIPLIERS),
|
|
dragState = movementUtils.applyEasing(deltaTime, 'easeOut', settings, currentVelocities, ACCELERATION_MULTIPLIERS);
|
|
|
|
currentVelocities.integrate(targetState, currentVelocities, dragState, settings);
|
|
|
|
var currentSpeed = Vec3.length(currentVelocities.translation),
|
|
targetSpeed = Vec3.length(movementState.translation),
|
|
verticalHold = movementState.isGrounded && settings.stayGrounded && Math.abs(headPitch) < STAYGROUNDED_PITCH_THRESHOLD;
|
|
|
|
var deltaOrientation = Quat.fromVec3Degrees(Vec3.multiply(deltaTime, currentVelocities.rotation)),
|
|
targetOrientation = Quat.normalize(Quat.multiply(currentOrientation, deltaOrientation));
|
|
|
|
var targetVelocity = Vec3.multiplyQbyV(targetOrientation, currentVelocities.translation);
|
|
|
|
if (verticalHold) {
|
|
targetVelocity.y = 0;
|
|
}
|
|
|
|
var deltaPosition = Vec3.multiply(deltaTime, targetVelocity);
|
|
|
|
_resetMyAvatarMotor(pendingChanges);
|
|
|
|
if (!independentCamera) {
|
|
var DriveModes = movementUtils.DriveModes;
|
|
switch (settings.driveMode) {
|
|
case DriveModes.MOTOR: {
|
|
if (currentSpeed > EPSILON || targetSpeed > EPSILON) {
|
|
var motorTimescale = (currentSpeed > EASED_MOTOR_THRESHOLD ? EASED_MOTOR_TIMESCALE : DEFAULT_MOTOR_TIMESCALE);
|
|
var motorPitch = Quat.fromPitchYawRollDegrees(headPitch, 180, 0),
|
|
motorVelocity = Vec3.multiplyQbyV(motorPitch, currentVelocities.translation);
|
|
if (verticalHold) {
|
|
motorVelocity.y = 0;
|
|
}
|
|
Object.assign(pendingChanges.MyAvatar, {
|
|
motorVelocity: motorVelocity,
|
|
motorTimescale: motorTimescale
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case DriveModes.THRUST: {
|
|
var thrustVector = currentVelocities.translation,
|
|
maxThrust = settings.translation.maxVelocity,
|
|
thrust;
|
|
if (targetSpeed > EPSILON) {
|
|
thrust = movementUtils.calculateThrust(maxThrust * 5, thrustVector, previousValues.thrust);
|
|
} else if (currentSpeed > 1 && Vec3.length(previousValues.thrust) > 1) {
|
|
thrust = Vec3.multiply(-currentSpeed / 10.0, thrustVector);
|
|
} else {
|
|
thrust = Vec3.ZERO;
|
|
}
|
|
if (thrust) {
|
|
thrust = Vec3.multiplyQbyV(MyAvatar[orientationProperty], thrust);
|
|
if (verticalHold) {
|
|
thrust.y = 0;
|
|
}
|
|
}
|
|
previousValues.thrust = pendingChanges.MyAvatar.setThrust = thrust;
|
|
break;
|
|
}
|
|
case DriveModes.POSITION: {
|
|
pendingChanges.MyAvatar.position = Vec3.sum(currentPosition, deltaPosition);
|
|
break;
|
|
}
|
|
default: {
|
|
throw new Error('unknown driveMode: ' + settings.driveMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
var finalOrientation;
|
|
switch (Camera.mode) {
|
|
case 'mirror': // fall through
|
|
case 'independent':
|
|
targetOrientation = settings.preventRoll ? Quat.cancelOutRoll(targetOrientation) : targetOrientation;
|
|
var boomVector = Vec3.multiply(-currentVelocities.zoom.z, Quat.getFront(targetOrientation)),
|
|
deltaCameraPosition = Vec3.sum(boomVector, deltaPosition);
|
|
Object.assign(pendingChanges.Camera, {
|
|
position: Vec3.sum(Camera.position, deltaCameraPosition),
|
|
orientation: targetOrientation
|
|
});
|
|
break;
|
|
case 'entity':
|
|
finalOrientation = targetOrientation;
|
|
break;
|
|
default: // 'first person', 'third person'
|
|
finalOrientation = targetOrientation;
|
|
break;
|
|
}
|
|
|
|
if (settings.jitterTest) {
|
|
finalOrientation = Quat.multiply(MyAvatar[orientationProperty], Quat.fromPitchYawRollDegrees(0, 60 * deltaTime, 0));
|
|
// Quat.fromPitchYawRollDegrees(0, _utils.getRuntimeSeconds() * 60, 0)
|
|
}
|
|
|
|
if (finalOrientation) {
|
|
if (settings.preventRoll) {
|
|
finalOrientation = Quat.cancelOutRoll(finalOrientation);
|
|
}
|
|
previousValues.finalOrientation = pendingChanges.MyAvatar[orientationProperty] = Quat.normalize(finalOrientation);
|
|
}
|
|
|
|
if (!movementState.mouseSmooth && movementState.isRightMouseButton) {
|
|
// directly apply mouse pitch and yaw when mouse smoothing is disabled
|
|
_applyDirectPitchYaw(deltaTime, movementState, settings);
|
|
}
|
|
|
|
var endTime = _utils.getRuntimeSeconds();
|
|
var cycleTime = endTime - update.endTime;
|
|
update.endTime = endTime;
|
|
|
|
pendingChanges.submit();
|
|
|
|
if ((endTime - update.windowStartTime) > 3) {
|
|
update.momentaryFPS = (update.frameCount - update.windowStartFrame) /
|
|
(endTime - update.windowStartTime);
|
|
update.windowStartFrame = update.frameCount;
|
|
update.windowStartTime = endTime;
|
|
}
|
|
|
|
if (_debugChannel && update.windowStartFrame === update.frameCount) {
|
|
Messages.sendLocalMessage(_debugChannel, JSON.stringify({
|
|
threadFrames: update.threadFrames,
|
|
frame: update.frameCount,
|
|
threadMode: settings.threadMode,
|
|
driveMode: settings.driveMode,
|
|
orientationProperty: orientationProperty,
|
|
isGrounded: movementState.isGrounded,
|
|
targetAnimationFPS: settings.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ? settings.fps : undefined,
|
|
actualFPS: 1 / actualDeltaTime,
|
|
effectiveAnimationFPS: 1 / deltaTime,
|
|
seconds: {
|
|
startTime: startTime,
|
|
endTime: endTime
|
|
},
|
|
milliseconds: {
|
|
actualDeltaTime: actualDeltaTime * 1000,
|
|
deltaTime: deltaTime * 1000,
|
|
cycleTime: cycleTime * 1000,
|
|
calculationTime: (endTime - startTime) * 1000
|
|
},
|
|
finalOrientation: finalOrientation,
|
|
thrust: thrust,
|
|
maxVelocity: settings.translation,
|
|
targetVelocity: targetVelocity,
|
|
currentSpeed: currentSpeed,
|
|
targetSpeed: targetSpeed
|
|
}, 0, 2));
|
|
}
|
|
}
|
|
|
|
function _resetMyAvatarMotor(targetObject) {
|
|
if (MyAvatar.motorTimescale !== DEFAULT_MOTOR_TIMESCALE) {
|
|
targetObject.MyAvatar.motorTimescale = DEFAULT_MOTOR_TIMESCALE;
|
|
}
|
|
if (MyAvatar.motorReferenceFrame !== 'avatar') {
|
|
targetObject.MyAvatar.motorReferenceFrame = 'avatar';
|
|
}
|
|
if (Vec3.length(MyAvatar.motorVelocity)) {
|
|
targetObject.MyAvatar.motorVelocity = Vec3.ZERO;
|
|
}
|
|
}
|
|
|
|
function _applyDirectPitchYaw(deltaTime, movementState, settings) {
|
|
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
|
rotation = movementState.rotation,
|
|
speed = Vec3.multiply(-DEG_TO_RAD / 2.0, settings.rotation.speed);
|
|
|
|
var previousValues = globalState.previousValues,
|
|
pendingChanges = globalState.pendingChanges,
|
|
currentVelocities = globalState.currentVelocities;
|
|
|
|
var previous = previousValues.pitchYawRoll,
|
|
target = Vec3.multiply(deltaTime, Vec3.multiplyVbyV(rotation, speed)),
|
|
pitchYawRoll = Vec3.mix(previous, target, 0.5),
|
|
orientation = Quat.fromVec3Degrees(pitchYawRoll);
|
|
|
|
previousValues.pitchYawRoll = pitchYawRoll;
|
|
|
|
if (pendingChanges.MyAvatar.headOrientation || pendingChanges.MyAvatar.orientation) {
|
|
var newOrientation = Quat.multiply(MyAvatar[orientationProperty], orientation);
|
|
delete pendingChanges.MyAvatar.headOrientation;
|
|
delete pendingChanges.MyAvatar.orientation;
|
|
if (settings.preventRoll) {
|
|
newOrientation = Quat.cancelOutRoll(newOrientation);
|
|
}
|
|
MyAvatar[orientationProperty] = newOrientation;
|
|
} else if (pendingChanges.Camera.orientation) {
|
|
var cameraOrientation = Quat.multiply(Camera.orientation, orientation);
|
|
if (settings.preventRoll) {
|
|
cameraOrientation = Quat.cancelOutRoll(cameraOrientation);
|
|
}
|
|
Camera.orientation = cameraOrientation;
|
|
}
|
|
currentVelocities.rotation = Vec3.ZERO;
|
|
}
|
|
}
|