<!DOCTYPE html>
<html>
<head>
<title>VRM with BVH Animation Player Ver0.005</title>
<meta charset="utf-8">
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="./js/aframe-vrm.js"></script> <script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.158.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/"
}
}
</script>
</head>
<body>
<a-scene renderer="physicallyCorrectLights: true">
<a-entity
id="avatar"
vrm="src: ./vrm/tesA1_V0a.vrm"
vrm-anim bvh-loader
position="0 0 0"
rotation="0 180 0">
</a-entity>
<a-entity camera position="0 1.6 2.5" look-controls wasd-controls></a-entity>
<a-sky color="#ECECEC"></a-sky>
<a-plane position="0 0 0" rotation="-90 0 0" width="100" height="100" color="#FFFFFF" shadow></a-plane>
<a-light type="directional" color="#FFF" intensity="0.6" position="-1 2 2"></a-light>
<a-light type="ambient" color="#FFF" intensity="0.6"></a-light>
</a-scene>
<script type="module">
// A-Frameが準備したTHREEオブジェクトと、モジュールとして読み込むBVHLoaderを使います
const THREE = window.THREE;
import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';
// =================================================================
// ▼▼▼ BVHをロードし、AnimationClipとしてvrm-animに渡すコンポーネント ▼▼▼
// =================================================================
AFRAME.registerComponent('bvh-loader', {
init: function () {
console.log('✅ bvh-loader: コンポーネント初期化');
// VRMモデルの読み込み完了を待機
const vrmComponent = this.el.components.vrm;
if (vrmComponent && vrmComponent.vrm) {
this.onVrmLoaded(vrmComponent.vrm);
} else {
this.el.addEventListener('vrm-loaded', (e) => this.onVrmLoaded(e.detail.vrm), { once: true });
}
},
onVrmLoaded: function (vrm) {
console.log('✅ VRM読み込み完了。BVH処理を開始します。');
const BVH_URL = 'https://p-bookmark.sakura.ne.jp/junkerstock/vrm/8.bvh';
const loader = new BVHLoader();
loader.load(BVH_URL, (bvh) => {
console.log('✅ BVH読み込み成功。AnimationClipを再構築します。');
const boneMap = {
'J_Bip_C_Hips': 'hips', 'J_Bip_C_Spine': 'spine', 'J_Bip_C_Chest': 'chest',
'J_Bip_C_UpperChest': 'upperChest', 'J_Bip_C_Neck': 'neck', 'J_Bip_C_Head': 'head',
'J_Bip_L_Shoulder': 'leftShoulder', 'J_Bip_L_UpperArm': 'leftUpperArm',
'J_Bip_L_LowerArm': 'leftLowerArm', 'J_Bip_L_Hand': 'leftHand',
'J_Bip_R_Shoulder': 'rightShoulder', 'J_Bip_R_UpperArm': 'rightUpperArm',
'J_Bip_R_LowerArm': 'rightLowerArm', 'J_Bip_R_Hand': 'rightHand',
'J_Bip_L_UpperLeg': 'leftUpperLeg', 'J_Bip_L_LowerLeg': 'leftLowerLeg',
'J_Bip_L_Foot': 'leftFoot', 'J_Bip_L_ToeBase': 'leftToes',
'J_Bip_R_UpperLeg': 'rightUpperLeg', 'J_Bip_R_LowerLeg': 'rightLowerLeg',
'J_Bip_R_Foot': 'rightFoot', 'J_Bip_R_ToeBase': 'rightToes'
};
const newTracks = [];
bvh.clip.tracks.forEach(track => {
if (track.name.endsWith('.position')) return;
const bvhNodeName = track.name.split('.')[0];
const vrmBoneName = boneMap[bvhNodeName];
if (vrmBoneName) {
const vrmBoneNode = vrm.humanoid.getBoneNode(vrmBoneName);
if (vrmBoneNode) {
const newTrack = track.clone();
newTrack.name = `${vrmBoneNode.name}.${track.name.split('.')[1]}`;
newTracks.push(newTrack);
}
}
});
console.log(`✅ ${newTracks.length}個のトラックをマッピングしました。`);
// 再構築したトラックから、新しいアニメーションクリップを生成
const newClip = new THREE.AnimationClip('bvh_motion', -1, newTracks);
// --- ここが今回の核心部分です ---
console.log('✅ AnimationClip生成完了。vrm-animのidleMotionとして設定します。');
// 自前でAnimationMixerを使わず、vrm-animコンポーネントの機能(待機モーション上書き)を利用する
this.el.setAttribute('vrm-anim', 'idleMotion', newClip);
console.log('🎉 アニメーション適用完了!');
});
},
// このコンポーネントは一度設定するだけなので、tickハンドラは不要です
});
</script>
</body>
</html>
使用変数
-------( Function ) | |
boneMap | |
bvhNodeName | |
BVH_URL | |
charset | |
color | |
height | |
id | |
intensity | |
loader | |
name | |
newClip | |
newTrack | |
newTracks | |
position | |
renderer | |
rotation | |
src | |
THREE | |
track | |
type | |
vrm | |
vrmBoneName | |
vrmBoneNode | |
vrmComponent | |
width |