content/hifi-content/dave/walk-tools/walkTools/walkTools.js
2022-02-13 22:49:05 +01:00

482 lines
No EOL
22 KiB
JavaScript

//
// walkTools.js
// version 0.5
//
// Created by David Wooldridge, October 2014.
// Copyright © 2014 - 2016 David Wooldridge
//
// Core object for the walkTools procedural animation editing tools for use with walk.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// load up the tools
Script.include("walkToolsToolBar.js");
Script.include("walkToolsStats.js");
Script.include("walkToolsEditor.js");
Script.include("walkToolsLog.js");
Script.include("walkToolsOscilloscope.js");
//Script.include("walkToolsBezierEditor.js");
Script.include("walkToolsCameras.js");
//Script.include("walkToolsGrid.js");
Script.include("walkToolsBVHPlayer.js");
//Script.include("walkToolsEEGDisplay.js");
walkTools = (function () {
var _walkToolsEnabled = true;
// this needs removing
var _visibility = {
visible: true,
editorVisible: false,
statsVisibile: false,
logVisible: false,
scopeVisible: false,
bezierVisible: false,
bvhPlayerVisible: false,
ftWheelVisible: false,
gridVisible: false,
settingsVisible: false
}
// timing and metrics
var _frameStartTime = 0;
var _frameExecutionTimePeak = 0; // keep track of the longest frame execution time
var _cumulativeTime = 0;
var _localSpeedPeak = 0;
var _localAccelerationPeak = 0;
var _yawDeltaPeak = 0;
var _yawDeltaAccelerationPeak = 0;
var _nFrames = 0;
var _globalPhase = 0;
var _stride = {
animationName: "",
rightFootStrideMax: 0,
rightFootStrideMaxAt: 0,
leftFootStrideMax: 0,
leftFootStrideMaxAt: 0,
}
// helper function for stats display
function directionAsString(directionEnum) {
switch (directionEnum) {
case UP:
return 'Up';
case DOWN:
return 'Down';
case LEFT:
return 'Left';
case RIGHT:
return 'Right';
case FORWARDS:
return 'Forwards';
case BACKWARDS:
return 'Backwards';
case NONE:
return 'None';
default:
return 'Unknown';
}
};
// helper function for stats display
function stateAsString(state) {
switch (state) {
case STATIC:
return 'Static';
case SURFACE_MOTION:
return 'Surface motion';
case AIR_MOTION:
return 'Air motion';
case EDIT:
return 'Editing';
default:
return 'Unknown';
}
};
function dumpCurrentAnimation(optimise) {
// can either produce smaller, optimised animation file (rounded parameters, no un-used harmonics, Ctrl-Shift-Q)
// or a larger 'raw' animation file (no rounding, keep all un-used harmonics for later editing, Shift-Q)
const JOINTS_DP = 5; // TODO: experiment reducing this once animations get to polished standard
const HARMONICS_DP = 16; // TODO: experiment reducing this once animations get to polished standard
try {
if (optimise) {
// clear out any un-used harmonics in the actual animation file
for (joint in avatar.currentAnimation.harmonics) {
for (harmonicData in avatar.currentAnimation.harmonics[joint]) {
var numHarmonics = avatar.currentAnimation.harmonics[joint][harmonicData].numHarmonics;
var magnitudes = [];
var phaseAngles = [];
for (i = 0 ; i < numHarmonics ; i++) {
var magnitude = avatar.currentAnimation.harmonics[joint][harmonicData].magnitudes[i];
var phaseAngle = avatar.currentAnimation.harmonics[joint][harmonicData].phaseAngles[i];
magnitudes.push(magnitude);
phaseAngles.push(phaseAngle);
}
if (avatar.currentAnimation.harmonics[joint][harmonicData].numHarmonics === 0) {
delete avatar.currentAnimation.harmonics[joint][harmonicData];
print('Setting null for '+joint+' '+harmonicData);
} else {
avatar.currentAnimation.harmonics[joint][harmonicData].magnitudes = magnitudes;
avatar.currentAnimation.harmonics[joint][harmonicData].phaseAngles = phaseAngles;
}
}
if (Object.keys(avatar.currentAnimation.harmonics[joint]).length === 0) {
delete avatar.currentAnimation.harmonics[joint];
}
}
}
// export avatar.currentAnimation as json string when q key is pressed.
// reformat result at http://jsonformatter.curiousconcept.com/
print('___________________________________\n');
if (optimise) {
print('walk.js dumping optimised animation: ' + avatar.currentAnimation.name + '\n');
} else {
print('walk.js dumping animation: ' + avatar.currentAnimation.name + '\n');
}
print('___________________________________\n');
print(JSON.stringify(avatar.currentAnimation, function(key, val) {
if (optimise) {
if (isNaN(Number(key))) {
// shorten joint parameters
if (!isNaN(val)) {
val = Number(val).toFixed(JOINTS_DP);
}
} else {
// shorten harmonics parameters
if (!isNaN(val)) {
val = Number(val).toFixed(HARMONICS_DP);
}
}
}
return val;
}));
print('\n');
print('___________________________________\n');
print(avatar.currentAnimation.name + ' animation dumped\n');
print('___________________________________\n');
// dump to walkTools log also
walkToolsLog.clearLog();
walkToolsLog.setVisible(true);
walkToolsLog.logMessage(JSON.stringify(avatar.currentAnimation, function(key, val) {
if (optimise) {
if (isNaN(Number(key))) {
// shorten joint parameters
if (!isNaN(val)) {
val = Number(val).toFixed(JOINTS_DP);
}
} else {
// shorten harmonics parameters
if (!isNaN(val)) {
val = Number(val).toFixed(HARMONICS_DP);
}
}
}
return val;
}), false);
} catch (error) {
print('Error dumping animation file: ' + error.toString() + '\n');
walkToolsLog.logMessage('Error dumping animation file: ' + error.toString());
return;
}
}
function dumpPreRotations() {
try {
// export avatar pre rotations as json string
// reformat result at http://jsonformatter.curiousconcept.com/
print('___________________________________\n');
print('walk.js dumping pre-rotations\n');
print('___________________________________\n');
print(JSON.stringify(walkAssets.preRotations, function(key, val) {
return val;
}));
print('\n');
print('___________________________________\n');
print('pre-rotations dumped\n');
print('___________________________________\n');
// dump to walkTools log also
walkToolsLog.clearLog();
walkToolsLog.setVisible(true);
walkToolsLog.logMessage(JSON.stringify(walkAssets.preRotations, function(key, val) {
return val;
}), false);
} catch (error) {
print('Error dumping pre-rotations: ' + error.toString() + '\n');
walkToolsLog.logMessage('Error dumping pre-rotations: ' + error.toString() + '\n');
return;
}
}
var _shift = false;
var _control = false;
function keyPressEvent(event) {
if (event.text === "SHIFT") {
_shift = true;
}
if (event.text === "CONTROL") {
_control = true;
}
if (_shift && (event.text === 'q' || event.text === 'Q')) {
dumpCurrentAnimation(_control);
}
}
function keyReleaseEvent(event) {
if (event.text === "SHIFT") {
_shift = false;
}
if (event.text === "CONTROL") {
_control = false;
}
}
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
// public methods
return {
// often useful elsewhere too
dumpCurrentAnimation: dumpCurrentAnimation,
dumpPreRotations: dumpPreRotations,
stateAsString: stateAsString,
directionAsString: directionAsString,
nFrames: _nFrames,
framesElapsed: function() {
return _nFrames;
},
visibility: _visibility,
enableWalkTools: function(enabled) {
_walkToolsEnabled = enabled;
},
isEnabled: function() {
return _walkToolsEnabled;
},
// for walkTools editor
ftWheelPosition: function() {
var ftWheelPosition = walkToolsEditor.getFTWheelPosition();
return ftWheelPosition;
},
currentlySelectedJoint: function() {
var selectedJointName = walkToolsEditor.currentlySelectedJoint();
return selectedJointName;
},
// for walkToolsLog
toLog: walkToolsLog.logMessage,
// stats
beginProfiling: function(deltaTime) {
if (_walkToolsEnabled) {
//if (deltaTime > 0.02) {
// print('Warning: (walkTools) deltaTime excessive: '+(deltaTime*1000).toFixed(1)+'mS');
//}
_frameStartTime = new Date().getTime();
_cumulativeTime += deltaTime;
_nFrames = _nFrames++ >= Number.MAX_SAFE_INTEGER ? 0 : _nFrames++;
}
},
dumpState: function() {
if (_walkToolsEnabled) {
var currentState = 'State: ' + stateAsString(motion.state) + ' ' + directionAsString(motion.direction) + '\n\n' +
'Playing: '+ avatar.currentAnimation.name + '\n' +
'Velocity: ' + Vec3.length(motion.velocity).toFixed(3) + ' m/s\n' +
'Velocity.x: ' + motion.velocity.x.toFixed(3) + ' m/s\n' +
'Velocity.y: ' + motion.velocity.y.toFixed(3) + ' m/s\n' +
'Velocity.z: ' + motion.velocity.z.toFixed(3) + ' m/s\n' +
'Acceleration mag: ' + Vec3.length(motion.acceleration).toFixed(2) + ' m/s/s\n' +
'Directed acceleration: '+ motion.directedAcceleration.toFixed(2) + ' m/s/s\n' +
'Direction: '+ directionAsString(motion.direction) +
'Above surface: ' + avatar.distanceFromSurface.toFixed(3) + ' m\n' +
//'Under gravity: '+avatar.isUnderGravity + '\n' +
'Accelerating: '+ motion.isAccelerating + '\n' +
'Decelerating: '+ motion.isDecelerating + '\n' +
'Decelerating fast: '+ motion.isDeceleratingFast + '\n' +
'Coming to a halt: '+motion.isComingToHalt + '\n' +
'Walking speed: '+motion.isWalkingSpeed + '\n' +
'Flying speed: '+motion.isFlyingSpeed + '\n';// +
//'Coming in to land: '+avatar.isComingInToLand + '\n' +
//'Taking off: '+avatar.isTakingOff + '\n' +
//'On surface: '+avatar.isOnSurface + '\n';
return currentState;
} else {
return "walkTools disabled";
}
},
updateStats: function() {
if (_visibility.visible && _walkToolsEnabled) {
var stats = {};
var deltaTimeMS = motion.deltaTime * 1000;
var frameExecutionTime = new Date().getTime() - _frameStartTime;
var distanceFromSurface = avatar.distanceFromSurface;
if (distanceFromSurface < 0.0001) distanceFromSurface = 0;
else if (distanceFromSurface > 16384) distanceFromSurface = Infinity;
// update peak values
_frameExecutionTimePeak = frameExecutionTime > _frameExecutionTimePeak ?
frameExecutionTime : _frameExecutionTimePeak;
_localAccelerationPeak = Vec3.length(motion.acceleration) > _localAccelerationPeak ?
Vec3.length(motion.acceleration) : _localAccelerationPeak;
_localSpeedPeak = Vec3.length(motion.velocity) > _localSpeedPeak ?
Vec3.length(motion.velocity) : _localSpeedPeak;
if (motion.yawDelta > 0) {
_yawDeltaPeak = motion.yawDelta > _yawDeltaPeak ?
motion.yawDelta : _yawDeltaPeak;
} else if (motion.yawDelta < 0){
_yawDeltaPeak = motion.yawDelta < _yawDeltaPeak ?
motion.yawDelta : _yawDeltaPeak;
}
_yawDeltaAccelerationPeak = motion.yawDeltaAcceleration > _yawDeltaAccelerationPeak ?
motion.yawDeltaAcceleration : _yawDeltaAccelerationPeak;
// update visible displays
if (_visibility.editorVisible) {
//walkInterface.updateTransportPanel();
}
if (_visibility.statsVisible) {
stats.state = stateAsString(motion.state);
stats.nextState = stateAsString(motion.nextState);
stats.currentAnimation = avatar.currentAnimation.name;
stats.frameNumber = _nFrames;
stats.frameTime = deltaTimeMS;
stats.speed = Vec3.length(motion.velocity);
stats.acceleration = Vec3.length(motion.acceleration);
stats.direction = directionAsString(motion.direction);
stats.directedAcceleration = motion.directedAcceleration;
stats.yaw = motion.yaw;
stats.yawDelta = motion.yawDelta;
stats.yawDeltaAcceleration = motion.yawDeltaAcceleration;
stats.aboveSurface = distanceFromSurface;
stats.moving = motion.isMoving;
stats.walkingSpeed = motion.isWalkingSpeed;
stats.flyingSpeed = motion.isFlyingSpeed;
stats.accelerating = motion.isAccelerating;
stats.decelerating = motion.isDecelerating;
stats.deceleratingFast = motion.isDeceleratingFast;
stats.comingToAHalt = motion.isComingToHalt;
stats.strideMax = avatar.currentAnimation.calibration.strideMax;
stats.strideMaxAt = avatar.currentAnimation.calibration.strideMaxAt;
}
if (_visibility.statsVisible && _nFrames % 15 === 0) {
stats.frameExecutionTimePeak = _frameExecutionTimePeak;
stats.speedPeak = _localSpeedPeak;
stats.accelerationPeak = _localAccelerationPeak;
stats.yawDeltaPeak = _yawDeltaPeak;
stats.yawDeltaAccelerationPeak = _yawDeltaAccelerationPeak;
_frameExecutionTimePeak = 0;
_localSpeedPeak = 0;
_localAccelerationPeak = 0;
_yawDeltaPeak = 0;
_yawDeltaAccelerationPeak = 0;
}
walkToolsStats.updateStats(stats);
}
},
updateFrequencyTimeWheelStats: function(deltaTime, speed, wheelRadius, degreesTurnedSinceLastFrame) {
if (_visibility.visible && _walkToolsEnabled) {
// stride calibration
if (avatar.currentAnimation.name !== _stride.animationName) {
_stride = {
animationName: avatar.currentAnimation.name,
rightFootStrideMax: 0,
rightFootStrideMaxAt: 0,
leftFootStrideMax: 0,
leftFootStrideMaxAt: 0,
}
}/*
var footRPos = MyAvatar.getJointPosition("RightFoot");
var footLPos = MyAvatar.getJointPosition("LeftFoot");
var distanceBetweenFeet = Vec3.distance(footRPos, footLPos);
if (motion.frequencyTimeWheelPos < HALF_CYCLE) {
// right foot leading
if (distanceBetweenFeet > _stride.rightFootStrideMax) {
_stride.rightFootStrideMax = distanceBetweenFeet;
_stride.rightFootStrideMaxAt = motion.frequencyTimeWheelPos;
}
} else {
// left foot leading
if (distanceBetweenFeet > _stride.leftFootStrideMax) {
_stride.leftFootStrideMax = distanceBetweenFeet;
_stride.leftFootStrideMaxAt = motion.frequencyTimeWheelPos;
}
}*/
var distanceTravelled = speed * deltaTime;
var ftWheelAngularVelocity = speed / wheelRadius;
if (_visibility.ftWheelVisible) {
var wheelCoordinates = {
wheelXPos: 0,
wheelXEnd: 0,
wheelYPos: 0,
wheelYEnd: 0,
wheelZPos: 0,
wheelZEnd: 0
};
if (avatar.currentAnimation === avatar.selectedSideStepLeft ||
avatar.currentAnimation === avatar.selectedSideStepRight) {
// draw the frequency time turning around the z axis for sidestepping
var directionSign = 1;
if (motion.direction === RIGHT) directionSign = -1;
var yOffset = avatar.calibration.hipsToFeet - (wheelRadius / 1.2);
var sinWalkWheelPosition = wheelRadius * Math.sin(filter.degToRad(directionSign * motion.frequencyTimeWheelPos));
var cosWalkWheelPosition = wheelRadius * Math.cos(filter.degToRad(directionSign * -motion.frequencyTimeWheelPos));
wheelCoordinates.wheelXPos = {x: cosWalkWheelPosition, y: -sinWalkWheelPosition - yOffset, z: 0};
wheelCoordinates.wheelXEnd = {x: -cosWalkWheelPosition, y: sinWalkWheelPosition - yOffset, z: 0};
sinWalkWheelPosition = wheelRadius * Math.sin(filter.degToRad(-directionSign * motion.frequencyTimeWheelPos + 90));
cosWalkWheelPosition = wheelRadius * Math.cos(filter.degToRad(-directionSign * motion.frequencyTimeWheelPos + 90));
wheelCoordinates.wheelYPos = {x: cosWalkWheelPosition, y: sinWalkWheelPosition - yOffset, z: 0};
wheelCoordinates.wheelYEnd = {x: -cosWalkWheelPosition, y: -sinWalkWheelPosition - yOffset, z: 0};
//.updateFTWheelDisplay(wheelCoordinates);
} else {
// draw the frequency time turning around the x axis for walking forwards or backwards
var forwardModifier = 1;
if (motion.direction === BACKWARDS) forwardModifier = -1;
var yOffset = 0;//- avatar.calibration.hipsToFeet - (wheelRadius / 1.2);
sinFTWheelPosition = wheelRadius * Math.sin(filter.degToRad(forwardModifier * motion.frequencyTimeWheelPos));
cosFTWheelPosition = wheelRadius * Math.cos(filter.degToRad(forwardModifier * motion.frequencyTimeWheelPos));
wheelCoordinates.wheelYPos = {x: 0, y: sinFTWheelPosition - yOffset, z: cosFTWheelPosition};
wheelCoordinates.wheelYEnd = {x: 0, y: -sinFTWheelPosition - yOffset, z: -cosFTWheelPosition};
wheelCoordinates.wheelZPos = {x: 0, y: -sinFTWheelPosition - yOffset, z: cosFTWheelPosition};
wheelCoordinates.wheelZEnd = {x: 0, y: sinFTWheelPosition - yOffset, z: -cosFTWheelPosition};
//.updateFTWheelDisplay(wheelCoordinates);
}
}
if (_visibility.statsVisible) {
var stats = {};
stats.wheelPositon = motion.frequencyTimeWheelPos;
stats.hipsToFeet = avatar.calibration.hipsToFeet;
stats.stride = avatar.currentAnimation.calibration.strideLength;
stats.wheelRadius = wheelRadius;
stats.ftWheelAngularVelocity = ftWheelAngularVelocity;
stats.degreesToTurn = degreesTurnedSinceLastFrame;
stats.distanceTravelled = distanceTravelled;
stats.strideInfo = _stride;
walkToolsStats.updateStats(stats);
}
}
}
}
})();