概要
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();
成果物
以上。