Hi, I know I’m just a bit late to the convo
, but I’ve been having the same issue and think I’ve found an okay solution.
Here, I manually assign the joints of my BVH animation to my GLTF model, parse the given quaternions from the BVH file, and apply them to my GLTF model from its rest position (T-Pose).
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { BVHLoader } from 'three/examples/jsm/loaders/BVHLoader.js';
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
import { cameraNear, cos, mod } from 'three/src/nodes/TSL.js';
const bvhToGltf = {
"Hips": "mixamorigHips_01",
"Spine": "mixamorigSpine_02",
"Spine1": "mixamorigSpine1_03",
"Neck": "mixamorigNeck_05",
"Head": "mixamorigHead_06",
"LeftShoulder": "mixamorigLeftShoulder_08",
"LeftArm": "mixamorigLeftArm_09",
"LeftForeArm": "mixamorigLeftForeArm_010",
"LeftHand": "mixamorigLeftHand_011",
"LeftHandThumb": "mixamorigLeftHandThumb1_012", // Assumed first thumb bone
"L_Wrist_End": "mixamorigLeftHandMiddle1_020", // Assumed first middle finger bone
"RightShoulder": "mixamorigRightShoulder_032",
"RightArm": "mixamorigRightArm_033",
"RightForeArm": "mixamorigRightForeArm_034",
"RightHand": "mixamorigRightHand_035",
"RightHandThumb": "mixamorigRightHandThumb1_036", // Assumed first thumb bone
"R_Wrist_End": "mixamorigRightHandMiddle1_044", // Assumed first middle finger bone
"LeftUpLeg": "mixamorigLeftUpLeg_056",
"LeftLeg": "mixamorigLeftLeg_057",
"LeftFoot": "mixamorigLeftFoot_058",
"LeftToeBase": "mixamorigLeftToeBase_059",
"RightUpLeg": "mixamorigRightUpLeg_061",
"RightLeg": "mixamorigRightLeg_00", // Verify if "00" is correct
"RightFoot": "mixamorigRightFoot_062",
"RightToeBase": "mixamorigRightToeBase_063"
};
/*********************************** Scene ***********************************/
const scene = new THREE.Scene();
scene.add(new THREE.AxesHelper(5))
// Camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 100);
camera.position.set(0, 5, 4);
// Lights
const light = new THREE.DirectionalLight(0xffffff, 5);
light.position.set(1, 3, 2);
scene.add(light);
const ambientLight = new THREE.AmbientLight()
scene.add(ambientLight)
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1, 0)
scene.add(new THREE.GridHelper(10, 10));
/*********************************** Scene ***********************************/
const gltfLoader = new GLTFLoader()
let model;
let joints = {};
let skinnedMesh;
let skeleton;
let initQuats = {};
let quats = {};
let hipLocs = [];
const loader = new BVHLoader();
loader.load('0005_2FeetJump001.bvh', result => {
const sourceSkeleton = result.skeleton;
const clip = result.clip;
console.log(clip);
let rawHipLocs = clip.tracks[0].values;
console.log(rawHipLocs);
for (let x_id = 0; x_id < rawHipLocs.length; x_id += 3){
hipLocs.push(new THREE.Vector3(rawHipLocs[x_id], rawHipLocs[x_id+1], rawHipLocs[x_id+2]));
}
for (let bone = 1; bone < clip.tracks.length; bone += 2){
const qts = clip.tracks[bone].values;
const nameRaw = clip.tracks[bone].name;
const index = nameRaw.indexOf(".");
const name = index !== -1 ? nameRaw.substring(0, index) : str;
console.log(name);
quats[name] = [];
for (let i = 0; i < qts.length; i+= 4){
const quat = new THREE.Quaternion(qts[i], qts[i+1], qts[i+2], qts[i+3]);
quats[name].push(quat);
}
}
console.log(hipLocs);
gltfLoader.load('y_bot_from_mixamo/scene.gltf', (gltf) => {
model = gltf.scene;
model.scale.set(2, 2, 2)
scene.add(model);
model.traverse(o => {
if (o.isBone) {
joints[o.name] = o;
initQuats[o.name] = o.quaternion.clone()
}
if (o.isSkinnedMesh) {
skinnedMesh = o;
skeleton = o.skeleton;
}
});
});
});
function getLocalQuat(bone, quat){
const parentWorldQuat = bone.parent.getWorldQuaternion(new THREE.Quaternion());
return parentWorldQuat.clone().invert().multiply(quat).multiply(parentWorldQuat);
}
let iQuat = new THREE.Quaternion().normalize();
function applyFrame(frame){
for (let bvhBoneName of Object.keys(bvhToGltf)){
let gltfBone = joints[bvhToGltf[bvhBoneName]];
let quat = quats[bvhBoneName][frame];
let localQuat = getLocalQuat(gltfBone, quat);
gltfBone.quaternion.copy(initQuats[bvhToGltf[bvhBoneName]]).premultiply(localQuat);
}
let hip = joints["mixamorigHips_01"];
hip.position.copy(hipLocs[frame]);
}
let currentFrame = 0;
const maxFrame = 2574;
window.addEventListener('keydown', (event) => {
if (event.key === 'ArrowRight') {
currentFrame = Math.min(currentFrame+10, maxFrame);
applyFrame(currentFrame);
}
else if (event.key === 'ArrowLeft') {
currentFrame = Math.max(currentFrame - 1, 0);
applyFrame(currentFrame);
}
});
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
animate();
Not sure that this will work for all applications, but I just thought I’d post my solution here in case it helps anyone!