482 lines
No EOL
22 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})(); |