298 lines
No EOL
11 KiB
JavaScript
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;
|
|
}; |