What's the best way to load BVH anim and to apply it dinamically to an existing rigged mesh (using Threejs!)?

I have a 3D character with specific rig and bones. I want to generate many animations secuences (in BVH format) that will be applied later dinamically to that character.
At this moment, I can’t find a method to obtain a good result.

I need a general orientation about the best way arround this approatch.

Any comment will be welcome!
Thanks!

There is a BVHLoader and a respective example that illustrates the usage.

https://threejs.org/examples/webgl_loader_bvh.html

You can use an instance of SkinnedMesh for your character model and apply the parsed skeleton to it.

Thanks for your reply!
Actually, I’ve already reviewed that and many other examples. And I have no problem representing the skeleton and the animation contained in a BVH file.
My problem is precisely how to associate that animation with the SkinnedMesh.
You could help me if you answer the following questions:
The SkinnedMesh must have a skeleton equal to the one in the BVH file? Or the SkinnedMesh does not need to have a skeleton and in that case, how is the animation associated?
Is it necessary to make some previous association between the skeleton of the BVH with the skeleton of the SkinnedMesh?
It will be enough if the BVH and the SkinnedMesh have the same skeleton and each bone has the attribute “name” exactly the same in both models for each bone?

Thank you!

AFAIK, the skeleton parsed by BVHLoader should be assigned to SkinnedMesh.skeleton via the respective .bind() method. Can you try the following code?

skinnedMesh.bind( skeleton );

It would be also easier to help you if you provide a live example with your current progress.

I know this is a shot in the dark but did you ever figure this out?

Can you show me how to do this if I set up a codeBox?

My proposed solution does not work if the skeletons of BVH and asset do not match, see Retargeting animation to Mixamo rig.

I have a bvh that matches a model… Im not binding correctly to the model. I’m still getting… THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton

I’ve been stuck on this for weeks. Can you please take a look/

Dude! Did you figure it out? I’m having the exactly same problem

Hi, I know I’m just a bit late to the convo :grin:, 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!