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

298 lines
No EOL
11 KiB
JavaScript

//
// BVH loader adapted from http://jaanga.github.io/cookbook/bvh-reader/readme-reader.html by Jaanga
//
// Adapted for HiFi JS: David Wooldridge, August 2015
//
Bvh = {};
Bvh.numJoints = 0;
Bvh.nodes = [];
Bvh.parseData = function (data, _translationScale) {
var _this = Bvh;
_this.data = data.split(/\s+/g);
_this.channels = [];
_this._translationScale = _translationScale;
done = false;
while (!done) {
var nextBit = _this.data.shift();
switch (nextBit) {
case 'ROOT':
_this.root = _this.parseNode(_this.data);
break;
case 'MOTION':
_this.data.shift();
_this.numFrames = parseInt( _this.data.shift() );
_this.data.shift();
_this.data.shift();
_this.secsPerFrame = parseFloat(_this.data.shift());
done = true;
}
}
_this.startTime = Date.now();
};
// convert Euler rotations to angles using Three.js objects
/* no longer working, as new QT JS doesn't support get/set, but THREE.js uses them everywhere - re-factoring...
Bvh.eulerRotationsToAngles = function(rotations, rotationOrder) {
var euler = new THREE.Euler(
filter.degToRad(rotations.x),
filter.degToRad(rotations.y),
filter.degToRad(rotations.z),
rotationOrder);
print('using euler.x = '+euler.x.toFixed(3));
var quaternion = new THREE.Quaternion();
quaternion.setFromEuler(euler);
var quat = {
x: quaternion.x,
y: quaternion.y,
z: quaternion.z,
w: quaternion.w
};
return Quat.safeEulerAngles(quat);
}*/
// working
Bvh.eulerRotationsToAngles = function(rotations, rotationOrder) {
if (rotationOrder === "ZXY") {
var quatZ = Quat.fromPitchYawRollDegrees( 0, 0, rotations.z);
var quatX = Quat.fromPitchYawRollDegrees( rotations.x, 0, 0);
var quatY = Quat.fromPitchYawRollDegrees( 0, rotations.y, 0);
var finalQuat = { x:0, y:0, z:0, w:1 };
finalQuat = Quat.multiply(finalQuat, quatZ);
finalQuat = Quat.multiply(finalQuat, quatX);
finalQuat = Quat.multiply(finalQuat, quatY);
rotations = Quat.safeEulerAngles(finalQuat);
}
return rotations;
}
// humbleTim's method - also working, using hifi glm polyfill
/*Script.include('https://cdn.rawgit.com/humbletim/54ba84a38d0600fa1661/raw/25bb203cbe917b9c48fb78f8b5300482539a6442/polyfill-hifi-glm.js');
Bvh.eulerRotationsToAngles = function(rotations, rotationOrder) {
var rot = glm.vec3( { x:rotations.x, y:rotations.y, z:rotations.z } );
var q = glm.quat( glm.radians( rot ) );
return glm.degrees( glm.eulerAngles( q ) );
}*/
// args: jointName, then references to values arrays, translation = false
Bvh.getChannels = function(jointName, x, y, z, translation) {
if (translation === undefined) {
translation = false;
}
for (var frameNumber = 0; frameNumber < this.numFrames; frameNumber++) {
var n = frameNumber % this.numFrames * this.channels.length;
for (var i = 0, len = this.channels.length; i < len; i++) {
var channel = this.channels[i];
if (channel.node.name === jointName) {
switch (channel.prop) {
case 'Xrotation':
if (!translation) {
x.push(this.data[n]);
}
break;
case 'Yrotation':
if (!translation) {
y.push(this.data[n]);
}
break;
case 'Zrotation':
if (!translation) {
z.push(this.data[n]);
}
break;
case 'Xposition':
if (translation) {
x.push(this.data[n]);
}
break;
case 'Yposition':
if (translation) {
y.push(this.data[n]);
}
break;
case 'Zposition':
if (translation) {
z.push(this.data[n]);
}
break;
default:
print('Warning: missing or unrecognised channel property in walkTools bvh importer: '+ch.prop);
break;
}
}
n++;
}
}
}
Bvh.animate = function(frame) {
// use the standard walk.js animation buffer to store keyframe values as offset parameters for each frame
var buffer = walkAssets.createAnimationBuffer("bvhData");
var ch, frame, n;
n = frame % this.numFrames * this.channels.length;
try {
for (var i = 0, len = this.channels.length; i < len; i++) {
ch = this.channels[i];
if (buffer.joints[ch.node.name]) {
switch (ch.prop) {
case 'Xrotation':
buffer.joints[ch.node.name].pitchOffset = this.data[n];
break;
case 'Yrotation':
buffer.joints[ch.node.name].yawOffset = this.data[n];
break;
case 'Zrotation':
buffer.joints[ch.node.name].rollOffset = this.data[n];
break;
case 'Xposition':
buffer.joints[ch.node.name].swayOffset = this.data[n];
break;
case 'Yposition':
buffer.joints[ch.node.name].bobOffset = this.data[n];
break;
case 'Zposition':
buffer.joints[ch.node.name].thrustOffset = this.data[n];
break;
default:
print('Warning: missing or unrecognised channel property in walkTools bvh player: '+ch.prop);
break;
}
}
n++;
}
} catch (e) {
print('Error on joint '+ch.node.name);
print('Error: '+e.toString());
}
try {
// the buffer's offsets have been filled for this frame, time to render
for (jointName in buffer.joints) {
var preRotation = {x: 0, y: 0, z: 0};
var bvhRotation = {x: 0, y: 0, z: 0};
var finalRotation = {x: 0, y: 0, z: 0};
var preRotationQ = {x: 0, y: 0, z: 0, w: 1};
var bvhRotationQ = {x: 0, y: 0, z: 0, w: 1};
var finalRotationQ = {x: 0, y: 0, z: 0, w: 1};
var iKChain = walkAssets.animationReference.joints[jointName].IKChain;
// deal with Hips translation first
if (jointName === "Hips") {
var hipsTranslations = {
x: this._translationScale * buffer.joints["Hips"].swayOffset,
y: this._translationScale * buffer.joints["Hips"].bobOffset,
z: this._translationScale * buffer.joints["Hips"].thrustOffset
};
// ensure skeleton offsets are within the 1m limit
hipsTranslations.x = hipsTranslations.x > 1 ? 1 : hipsTranslations.x;
hipsTranslations.x = hipsTranslations.x < -1 ? -1 : hipsTranslations.x;
hipsTranslations.y = hipsTranslations.y > 1 ? 1 : hipsTranslations.y;
hipsTranslations.y = hipsTranslations.y < -1 ? -1 : hipsTranslations.y;
hipsTranslations.z = hipsTranslations.z > 1 ? 1 : hipsTranslations.z;
hipsTranslations.z = hipsTranslations.z < -1 ? -1 : hipsTranslations.z;
MyAvatar.setSkeletonOffset(hipsTranslations);
}
if (avatar.isUsingHiFiPreRotations) {
// using the HiFi supplied pre-rotations
var jointNumber = MyAvatar.getJointIndex(jointName);
if (jointNumber < 0) {
print('jaanga parser error: joint number is not defined for '+jointName);
} else {
preRotationQ = MyAvatar.getDefaultJointRotation(jointNumber);
}
}
bvhRotation =
{
x: Number(buffer.joints[jointName].pitchOffset),
y: Number(buffer.joints[jointName].yawOffset),
z: Number(buffer.joints[jointName].rollOffset)
};
bvhRotation = this.eulerRotationsToAngles(bvhRotation, "ZXY");
bvhRotationQ = Quat.fromPitchYawRollDegrees(bvhRotation.x, bvhRotation.y, bvhRotation.z);
finalRotationQ = Quat.multiply(preRotationQ, bvhRotationQ);
/*if (jointName === walkTools.currentlySelectedJoint()) {
walkToolsOscilloscope.updateScopeData(
{
title: 'Jaanga BVH data',
metaDataLabel: "",
metaData: "",
joint: jointName,
iKChain: walkAssets.animationReference.joints[jointName].IKChain,
ch1: finalRotation.x,
ch2: finalRotation.y,
ch3: finalRotation.z
}
);
}*/
MyAvatar.setJointRotation(jointName, finalRotationQ);
}
} catch (e) {
print('Error calculating rotation in jaanga bvh parser: '+e.toString());
}
};
Bvh.parseNode = function(data) {
var geometry, material, n, t, done;
var nextBit = data.shift();
var node = {};
node.name = nextBit;
node.rotationOrder = 'ZXY';
node.childNodes = [];
done = false;
while ( !done ) {
nextBit = data.shift();
switch (nextBit) {
case 'OFFSET':
node.position = { x: parseFloat(data.shift()), y: parseFloat(data.shift()), z: parseFloat(data.shift()) };
node.offset = node.position;
break;
case 'CHANNELS':
n = parseInt(data.shift()); // number of channels
for ( var i = 0; 0 <= n ? i < n : i > n; 0 <= n ? i++ : i-- ) {
this.channels.push({
node: node,
prop: data.shift()
});
}
break;
case 'JOINT':
node.childNodes.push(this.parseNode(data));
break;
case 'End':
node.childNodes.push(this.parseNode(data));
break;
case '}':
done = true;
break;
}
}
return node;
};