Created
November 30, 2022 06:48
-
-
Save 20m61/2fa3f8b5e706db0d11d3e9b87e4a7204 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from "three"; | |
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; | |
import { BVHLoader } from "three/examples/jsm/loaders/BVHLoader"; | |
import { VRM, VRMSchema } from "@pixiv/three-vrm"; | |
// シーンの準備 | |
const scene = new THREE.Scene(); | |
// カメラの準備 | |
const camera = new THREE.PerspectiveCamera( | |
45, | |
window.innerWidth / window.innerHeight, | |
0.1, | |
1000 | |
); | |
// レンダラーの準備 | |
const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.setClearColor(0x7fbfff, 1.0); | |
document.body.appendChild(renderer.domElement); | |
// ライトの準備 | |
const light = new THREE.DirectionalLight(0xffffff); | |
light.position.set(-1, 1, -1).normalize(); | |
scene.add(light); | |
// アニメーションの準備 | |
let mixer = null; | |
// VRMの読み込み | |
const loader = new GLTFLoader(); | |
loader.load("./alicia.vrm", (gltf) => { | |
VRM.from(gltf).then((vrm) => { | |
// 姿勢の指定 | |
vrm.scene.position.y = -1; | |
vrm.scene.position.z = -3; | |
vrm.scene.rotation.y = Math.PI; | |
// シーンへの追加 | |
scene.add(vrm.scene); | |
// BVHの読み込み | |
const loader = new BVHLoader(); | |
//01_01, 85_02 | |
loader.load("85_02.bvh", function (bvh) { | |
// AnimationClipの生成 | |
const clip = createClip(vrm, bvh); | |
// AnimationMixerの生成 | |
mixer = new THREE.AnimationMixer(vrm.scene); | |
mixer.clipAction(clip).setEffectiveWeight(1.0).play(); | |
}); | |
}); | |
}); | |
// アニメーションループの開始 | |
let lastTime = new Date().getTime(); | |
function animate() { | |
requestAnimationFrame(animate); | |
// AnimationMixerの更新 | |
let time = new Date().getTime(); | |
if (mixer) mixer.update(time - lastTime); | |
lastTime = time; | |
renderer.render(scene, camera); | |
} | |
animate(); | |
// トラックの取得 | |
function findTrack(name, tracks) { | |
for (let i = 0; i < tracks.length; i++) { | |
if (tracks[i].name == name) return tracks[i]; | |
} | |
return null; | |
} | |
// 配列をQuaternionに変換 | |
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 == "rButtock" || id == "lButtock") { | |
const id2 = id == "rButtock" ? "rThigh" : "lThigh"; | |
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 == "hip") { | |
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 = [ | |
VRMSchema.HumanoidBoneName.Head, | |
VRMSchema.HumanoidBoneName.Neck, | |
VRMSchema.HumanoidBoneName.Chest, | |
VRMSchema.HumanoidBoneName.Spine, | |
VRMSchema.HumanoidBoneName.Hips, | |
VRMSchema.HumanoidBoneName.RightShoulder, | |
VRMSchema.HumanoidBoneName.RightUpperArm, | |
VRMSchema.HumanoidBoneName.RightLowerArm, | |
VRMSchema.HumanoidBoneName.RightHand, | |
VRMSchema.HumanoidBoneName.LeftShoulder, | |
VRMSchema.HumanoidBoneName.LeftUpperArm, | |
VRMSchema.HumanoidBoneName.LeftLowerArm, | |
VRMSchema.HumanoidBoneName.LeftHand, | |
VRMSchema.HumanoidBoneName.RightUpperLeg, | |
VRMSchema.HumanoidBoneName.RightLowerLeg, | |
VRMSchema.HumanoidBoneName.RightFoot, | |
VRMSchema.HumanoidBoneName.LeftUpperLeg, | |
VRMSchema.HumanoidBoneName.LeftLowerLeg, | |
VRMSchema.HumanoidBoneName.LeftFoot, | |
]; | |
const idList = [ | |
"head", | |
"neck", | |
"chest", | |
"abdomen", | |
"hip", | |
"rCollar", | |
"rShldr", | |
"rForeArm", | |
"rHand", | |
"lCollar", | |
"lShldr", | |
"lForeArm", | |
"lHand", | |
"rButtock", | |
"rShin", | |
"rFoot", | |
"lButtock", | |
"lShin", | |
"lFoot", | |
]; | |
const bones = nameList.map((boneName) => { | |
return vrm.humanoid.getBoneNode(boneName); | |
}); | |
// AnimationClipの生成 | |
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment