概要
three.jsでanimation systemを、理解したかった。
練習問題、やってみた。
練習問題
vrmのAnimationClipをBVHから変換して適合せよ。
写真
サンプルコード
let mixer;
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, 500 / 400, 0.1, 1000)
camera.position.set(0, 1.2, -1.9)
camera.rotation.set(0, Math.PI, 0)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(500, 400)
document.body.appendChild(renderer.domElement)
const controls = new THREE.OrbitControls(camera, renderer.domElement);
const directionalLight = new THREE.DirectionalLight('#ffffff', 1)
directionalLight.position.set(1, 1, 1)
scene.add(directionalLight)
const loader = new THREE.GLTFLoader();
loader.load('https://drumath2237.github.io/three-vrm-test/models/undefined-chan-toon.vrm',
  (gltf) => {
    THREE.VRM.from(gltf).then((vrm) => {
      scene.add(vrm.scene);
    	const loader = new THREE.BVHLoader();
	    loader.load("https://cdn.rawgit.com/una-dinosauria/cmu-mocap/master/data/001/01_01.bvh", function(bvh) {
				//console.log(bvh);
    		const clip = createClip(vrm, bvh);
				//console.log(clip);
	    	mixer = new THREE.AnimationMixer(vrm.scene);
		   	mixer.clipAction(clip).setEffectiveWeight(1.0).play();
      });
    });
  },
	(progress) => console.log("Loading model...", 100.0 * (progress.loaded / progress.total), "%"),
  (error) => console.error(error)
)
function findTrack(name, tracks) {
	for (let i = 0; i < tracks.length; i++)
	{
		if (tracks[i].name == name)
			return tracks[i];
	}
	alert(name);
	return null;
}
function values2quaternion(values, i) {
	return new THREE.Quaternion(values[i * 4], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
}
function createKeys(id, tracks) {
	const posTrack = findTrack(".bones[" + id + "].position", tracks);
	const rotTrack = findTrack(".bones[" + id + "].quaternion", tracks);
	const keys = [];
	const rate = 0.008;
	for (let i = 0; i < posTrack.times.length; i++)
	{
		const key = {};
		key["time"] = parseInt(posTrack.times[i] * 1000);
		if (id == "RHipJoint" || id == "LHipJoint")
		{
			//console.log(id);
			const id2 = id == "LHipJoint" ? "RightUpLeg" : "LeftUpLeg";
			let q1 = values2quaternion(rotTrack.values, i);
			const rotTrack2 = findTrack(".bones[" + id2 + "].quaternion", tracks);
			q1.multiply(values2quaternion(rotTrack2.values, i));
			key["rot"] = [-q1.x, q1.y, -q1.z, q1.w];
		}
		else
		{
			key["rot"] = [-rotTrack.values[i * 4], rotTrack.values[i * 4 + 1], -rotTrack.values[i * 4 + 2], rotTrack.values[i * 4 + 3],];
		}
		if (id == "Hips")
		{
			//console.log(id);
			key["pos"] = [-posTrack.values[i * 3] * rate, posTrack.values[i * 3 + 1] * rate, -posTrack.values[i * 3 + 2] * rate,];
		}
		keys.push(key);
	}
	if (keys.length == 0)
		return null;
	return keys;
}
function createClip(vrm, bvh) {
	const nameList = [
		THREE.VRMSchema.HumanoidBoneName.Head,
		THREE.VRMSchema.HumanoidBoneName.Neck,
		THREE.VRMSchema.HumanoidBoneName.Chest,
		THREE.VRMSchema.HumanoidBoneName.Spine,
		THREE.VRMSchema.HumanoidBoneName.Hips,
		THREE.VRMSchema.HumanoidBoneName.RightShoulder,
		THREE.VRMSchema.HumanoidBoneName.RightUpperArm,
		THREE.VRMSchema.HumanoidBoneName.RightLowerArm,
		THREE.VRMSchema.HumanoidBoneName.RightHand,
		THREE.VRMSchema.HumanoidBoneName.LeftShoulder,
		THREE.VRMSchema.HumanoidBoneName.LeftUpperArm,
		THREE.VRMSchema.HumanoidBoneName.LeftLowerArm,
		THREE.VRMSchema.HumanoidBoneName.LeftHand,
		THREE.VRMSchema.HumanoidBoneName.RightUpperLeg,
		THREE.VRMSchema.HumanoidBoneName.RightLowerLeg,
		THREE.VRMSchema.HumanoidBoneName.RightFoot,
		THREE.VRMSchema.HumanoidBoneName.LeftUpperLeg,
		THREE.VRMSchema.HumanoidBoneName.LeftLowerLeg,
		THREE.VRMSchema.HumanoidBoneName.LeftFoot,
	];
	const idList = [
		"Head",
		"Neck",
		"LowerBack",
		"Spine1",
		"Hips",
		"RightShoulder",
		"RightArm",
		"RightForeArm",
		"RightHand",
		"LeftShoulder",
		"LeftArm",
		"LeftForeArm",
		"LeftHand",
		"RHipJoint",
		"RightLeg",
		"RightFoot",
		"LHipJoint",
		"LeftLeg",
		"LeftFoot"
	];
	const bones = nameList.map((boneName) => {
		return vrm.humanoid.getBoneNode(boneName);
	});
	const hierarchy = [];
	for (let i = 0; i < idList.length; i++)
	{
		const keys = createKeys(idList[i], bvh.clip.tracks);
		if (keys != null)
		{
			hierarchy.push({
				keys: keys
			});
		}
	}
	const clip = THREE.AnimationClip.parseAnimation({
		hierarchy: hierarchy
	}, bones);
	clip.tracks.some((track) => {
		track.name = track.name.replace(/^\.bones\[([^\]]+)\].(position|quaternion|scale)$/, "$1.$2");
	});
	return clip;
}
function animate() {
	requestAnimationFrame(animate);
	let time = new Date().getTime();
	if (mixer)
		mixer.update(time - lastTime);
	lastTime = time;
	renderer.render(scene, camera);
}
animate();    
    
    
成果物
以上。
