junkerstock
 vrm-text-bvh1 

<!DOCTYPE html>
<html>
<head>
<title>VRM with BVH Animation Player</title>
<meta charset="utf-8">
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://unpkg.com/three@0.150.1/examples/js/loaders/BVHLoader.js"></script>

<script src="./js/aframe-vrm.js"></script>




<script>
// 元のカメラコントロールはそのまま
AFRAME.registerComponent('camera-relative-controls', { /* ...(内容は変更なしなので省略)... */ });

// ========= BVH再生用コンポーネント(デバッグログ出力機能付き)=========
AFRAME.registerComponent('bvh-player', {
schema: {
src: { type: 'string' },
loop: { type: 'boolean', default: true },
},

init: function () {
console.log('✅ bvh-player: コンポーネント初期化');
this.mixer = null;
this.vrmData = null;

this.el.addEventListener('vrm-loaded', (e) => {
console.log('✅ vrm-loaded: VRMモデルの読み込み完了', e.detail.vrm);
this.vrmData = e.detail.vrm;
this.loadBVH(this.data.src);
});
},

loadBVH: function(url) {
console.log(`⏳ bvh-player: BVHファイルの読み込みを開始... URL: ${url}`);
const loader = new THREE.BVHLoader();
loader.load(url, (bvh) => {
console.log('✅ bvh-player: BVHファイルの読み込み成功', bvh);

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'
};

console.log('⏳ bvh-player: ボーンのマッピングを開始...');
const newTracks = [];
bvh.clip.tracks.forEach(track => {
const bvhNodeName = track.name.split('.')[0];
const vrmBoneName = boneMap[bvhNodeName];

if (vrmBoneName) {
const vrmBoneNode = this.vrmData.humanoid.getBoneNode(vrmBoneName);
if (vrmBoneNode) {
console.log(` 👍 マッピング成功: ${bvhNodeName} -> ${vrmBoneName}`);
const newTrack = track.clone();
newTrack.name = `${vrmBoneNode.name}.${track.name.split('.')[1]}`;
newTracks.push(newTrack);
}
}
});
console.log(`✅ bvh-player: ボーンのマッピング完了。${newTracks.length}個のトラックをマッピングしました。`);

if (newTracks.length === 0) {
console.error('❌ エラー: マッピングされたボーンが一つもありません。boneMapのキー(左側の名前)がBVHファイル内のボーン名と一致しているか確認してください。');
return; // 処理を中断
}

const filteredTracks = newTracks.filter(track => !track.name.endsWith('.position'));
console.log(`✅ bvh-player: 位置情報を除外。${filteredTracks.length}個のトラックが残りました。`);

const newClip = new THREE.AnimationClip('bvh_animation', bvh.clip.duration, filteredTracks);

this.mixer = new THREE.AnimationMixer(this.el.object3D);
const action = this.mixer.clipAction(newClip);

if(this.data.loop) {
action.setLoop(THREE.LoopRepeat);
}
action.play();
console.log('✅ bvh-player: アニメーション再生を開始しました。', this.mixer);

},
// ---- 読み込み中の処理(今回は空) ----
undefined,
// ---- 読み込み失敗時の処理 ----
(error) => {
console.error('❌ bvh-player: BVHファイルの読み込みに失敗しました。', error);
console.error('CORSポリシーエラーの可能性があります。ローカルサーバーで実行しているか、BVHファイルのURLが正しいか確認してください。');
});
},

tick: function (time, timeDelta) {
if (this.mixer) {
this.mixer.update(timeDelta / 1000);
}
}
});

// カメラコントロールのコンポーネント定義は長いので、ここでは省略します。
// 前回のコードのものをそのままコピーしてください。
AFRAME.registerComponent('camera-relative-controls', {
schema: {
targetSpeed: { type: 'number', default: 5 },
acceleration: { type: 'number', default: 10 },
damping: { type: 'number', default: 8 },
brakingDeceleration: { type: 'number', default: 20 },
enabled: { type: 'boolean', default: true },
rotationSpeed: { type: 'number', default: 1.5 },
verticalSpeed: { type: 'number', default: 3 },
groundY: { type: 'number', default: 0 },
ceilingY: { type: 'number', default: 50 }
},
init: function () {
this.keys = {};
this.leftThumbstickInput = { x: 0, y: 0 };
this.rightThumbstickInput = { x: 0, y: 0 };
this.currentVelocity = new THREE.Vector3();
this.cameraDirection = new THREE.Vector3();
this.cameraRight = new THREE.Vector3();
this.moveDirection = new THREE.Vector3();
this.desiredVelocity = new THREE.Vector3();
this.cameraWorldQuaternion = new THREE.Quaternion();
this.rigEl = this.el;
this.cameraEl = this.el.querySelector('[camera]');

this.el.sceneEl.addEventListener('loaded', () => {
this.leftHand = document.getElementById('leftHand');
if (this.leftHand) { this.leftHand.addEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); }
this.rightHand = document.getElementById('rightHand');
if (this.rightHand) { this.rightHand.addEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); }
});

window.addEventListener('keydown', this.onKeyDown.bind(this));
window.addEventListener('keyup', this.onKeyUp.bind(this));
},
remove: function () {
window.removeEventListener('keydown', this.onKeyDown.bind(this));
window.removeEventListener('keyup', this.onKeyUp.bind(this));
if (this.leftHand) { try { this.leftHand.removeEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); } catch(e){} }
if (this.rightHand) { try { this.rightHand.removeEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); } catch(e){} }
},
onLeftThumbstickMoved: function (evt) { this.leftThumbstickInput.x = evt.detail.x; this.leftThumbstickInput.y = evt.detail.y; },
onRightThumbstickMoved: function (evt) { this.rightThumbstickInput.x = evt.detail.x; this.rightThumbstickInput.y = evt.detail.y; },
tick: function (time, timeDelta) {
if (!this.data.enabled || !this.cameraEl) return;

const data = this.data;
const dt = timeDelta / 1000;
const position = this.rigEl.object3D.position;

if (this.rigEl.sceneEl.is('vr-mode')) {
if (Math.abs(this.rightThumbstickInput.x) > 0.1) { this.rigEl.object3D.rotation.y -= this.rightThumbstickInput.x * data.rotationSpeed * dt; }
}

const cameraObject = this.cameraEl.object3D;
cameraObject.getWorldQuaternion(this.cameraWorldQuaternion);
this.cameraDirection.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion);
this.cameraRight.set(1, 0, 0).applyQuaternion(this.cameraWorldQuaternion);
this.cameraRight.y = 0;
this.cameraRight.normalize();
this.moveDirection.set(0, 0, 0);

if (this.keys['KeyW'] || this.keys['ArrowUp']) { this.moveDirection.add(this.cameraDirection); }
if (this.keys['KeyS'] || this.keys['ArrowDown']) { this.moveDirection.sub(this.cameraDirection); }
if (this.keys['KeyA'] || this.keys['ArrowLeft']) { this.moveDirection.sub(this.cameraRight); }
if (this.keys['KeyD'] || this.keys['ArrowRight']) { this.moveDirection.add(this.cameraRight); }
if (Math.abs(this.leftThumbstickInput.y) > 0.1) { this.moveDirection.add(this.cameraDirection.clone().multiplyScalar(-this.leftThumbstickInput.y)); }
if (Math.abs(this.leftThumbstickInput.x) > 0.1) { this.moveDirection.add(this.cameraRight.clone().multiplyScalar(this.leftThumbstickInput.x)); }

const isInputting = this.moveDirection.lengthSq() > 0.0001;
if (isInputting) { this.moveDirection.normalize(); }

let lerpFactor = data.damping;
if (isInputting) {
this.desiredVelocity.copy(this.moveDirection).multiplyScalar(data.targetSpeed);
lerpFactor = data.acceleration;
} else {
this.desiredVelocity.set(0,0,0);
lerpFactor = data.damping;
}
const effectiveLerpFactor = 1.0 - Math.exp(-lerpFactor * dt);
this.currentVelocity.lerp(this.desiredVelocity, effectiveLerpFactor);

if (this.currentVelocity.lengthSq() > 0.0001) {
position.add(this.currentVelocity.clone().multiplyScalar(dt));
}

let verticalMovement = 0;
if (this.rigEl.sceneEl.is('vr-mode') && Math.abs(this.rightThumbstickInput.y) > 0.1) {
verticalMovement = this.rightThumbstickInput.y * data.verticalSpeed * dt;
}

const nextY = position.y + verticalMovement;
position.y = THREE.MathUtils.clamp(nextY, data.groundY, data.ceilingY);
},
onKeyDown: function (event) { if (['KeyW', 'ArrowUp', 'KeyS', 'ArrowDown', 'KeyA', 'ArrowLeft', 'KeyD', 'ArrowRight'].includes(event.code)) { this.keys[event.code] = true; } },
onKeyUp: function (event) { if (this.keys[event.code] !== undefined) { delete this.keys[event.code]; } }
});
</script>












</head>
<body>
<a-scene>
<a-entity
id="avatar"
vrm="src: ./vrm/tesA1_V0a.vrm"
bvh-player="src: https://p-bookmark.sakura.ne.jp/junkerstock/vrm/8.bvh"
position="0 0 0"
rotation="0 180 0">
</a-entity>

<a-entity id="rig" position="0 0 2.5" camera-relative-controls="groundY: -1.5">
<a-entity id="player">
<a-entity id="camera" camera look-controls position="0 1.6 0"></a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right"></a-entity>
</a-entity>
</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>

</body>
</html>


使用変数

-------( Function )
action
boneMap
bvhNodeName
cameraDirection
cameraEl
cameraObject
cameraRight
charset
color
controls
currentVelocity
data
desiredVelocity
dt
eftThumbstickInput
eraWorldQuaternion
ffectiveLerpFactor
filteredTracks
ghtThumbstickInput
height
id
intensity
isInputting
keys
leftHand
length
lerpFactor
loader
mixer
moveDirection
name
newClip
newTrack
newTracks
nextY
player
position
rigEl
rightHand
rotation
src
track
type
url) { console.log -------( Function )
verticalMovement
vrm
vrmBoneName
vrmBoneNode
vrmData
width
x
y
ポーネント(デバッグログ出力機能付き)