feat(web): T-WEB-006 BVH/FBX → MediaPipe33 关键点预处理
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* 生成用于测试与验收的 BVH 样例(60 帧挥手 + 深蹲)。
|
||||
* 用法:node test/fixtures/generate-sample-bvh.mjs [输出路径]
|
||||
*/
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const outPath = process.argv[2] ?? join(here, 'sample.bvh');
|
||||
|
||||
const HIERARCHY = `HIERARCHY
|
||||
ROOT Hips
|
||||
{
|
||||
OFFSET 0.0 0.0 0.0
|
||||
CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
|
||||
JOINT Spine
|
||||
{
|
||||
OFFSET 0.0 12.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT Neck
|
||||
{
|
||||
OFFSET 0.0 14.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT Head
|
||||
{
|
||||
OFFSET 0.0 8.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
End Site
|
||||
{
|
||||
OFFSET 0.0 8.0 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
JOINT LeftShoulder
|
||||
{
|
||||
OFFSET 6.0 10.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT LeftArm
|
||||
{
|
||||
OFFSET 12.0 0.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT LeftForeArm
|
||||
{
|
||||
OFFSET 22.0 0.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT LeftHand
|
||||
{
|
||||
OFFSET 20.0 0.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
End Site
|
||||
{
|
||||
OFFSET 8.0 0.0 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JOINT RightShoulder
|
||||
{
|
||||
OFFSET -6.0 10.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT RightArm
|
||||
{
|
||||
OFFSET -12.0 0.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT RightForeArm
|
||||
{
|
||||
OFFSET -22.0 0.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT RightHand
|
||||
{
|
||||
OFFSET -20.0 0.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
End Site
|
||||
{
|
||||
OFFSET -8.0 0.0 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JOINT LeftUpLeg
|
||||
{
|
||||
OFFSET 5.0 -3.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT LeftLeg
|
||||
{
|
||||
OFFSET 0.0 -26.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT LeftFoot
|
||||
{
|
||||
OFFSET 0.0 -25.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT LeftToeBase
|
||||
{
|
||||
OFFSET 0.0 -4.0 10.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
End Site
|
||||
{
|
||||
OFFSET 0.0 -2.0 6.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JOINT RightUpLeg
|
||||
{
|
||||
OFFSET -5.0 -3.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT RightLeg
|
||||
{
|
||||
OFFSET 0.0 -26.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT RightFoot
|
||||
{
|
||||
OFFSET 0.0 -25.0 0.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
JOINT RightToeBase
|
||||
{
|
||||
OFFSET 0.0 -4.0 10.0
|
||||
CHANNELS 3 Zrotation Xrotation Yrotation
|
||||
End Site
|
||||
{
|
||||
OFFSET 0.0 -2.0 6.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MOTION
|
||||
`;
|
||||
|
||||
/** 通道顺序(与上面的 HIERARCHY 一致):Hips 6 通道 + 19 个关节各 3 通道 */
|
||||
const JOINT_ORDER = [
|
||||
'Hips',
|
||||
'Spine',
|
||||
'Neck',
|
||||
'Head',
|
||||
'LeftShoulder',
|
||||
'LeftArm',
|
||||
'LeftForeArm',
|
||||
'LeftHand',
|
||||
'RightShoulder',
|
||||
'RightArm',
|
||||
'RightForeArm',
|
||||
'RightHand',
|
||||
'LeftUpLeg',
|
||||
'LeftLeg',
|
||||
'LeftFoot',
|
||||
'LeftToeBase',
|
||||
'RightUpLeg',
|
||||
'RightLeg',
|
||||
'RightFoot',
|
||||
'RightToeBase',
|
||||
];
|
||||
const CHANNELS = 6 + 3 * (JOINT_ORDER.length - 1);
|
||||
const FRAMES = 60;
|
||||
const FRAME_TIME = 1 / 30;
|
||||
|
||||
const lines = [];
|
||||
for (let f = 0; f < FRAMES; f += 1) {
|
||||
const t = f / FRAMES;
|
||||
const phase = Math.sin(t * Math.PI * 2);
|
||||
const squat = (1 - Math.cos(t * Math.PI * 2)) / 2;
|
||||
|
||||
const values = [];
|
||||
for (const joint of JOINT_ORDER) {
|
||||
if (joint === 'Hips') {
|
||||
values.push(0, -squat * 12, 0); // Xposition Yposition Zposition
|
||||
values.push(phase * 6, 0, 0); // Zrotation Xrotation Yrotation
|
||||
continue;
|
||||
}
|
||||
const rot = { z: 0, x: 0, y: 0 };
|
||||
if (joint === 'LeftArm') rot.z = 20 + phase * 55;
|
||||
if (joint === 'RightArm') rot.z = -20 - phase * 55;
|
||||
if (joint === 'LeftForeArm') rot.x = -25 - phase * 30;
|
||||
if (joint === 'RightForeArm') rot.x = -25 + phase * 30;
|
||||
if (joint === 'LeftUpLeg') rot.x = -squat * 35;
|
||||
if (joint === 'RightUpLeg') rot.x = -squat * 35;
|
||||
if (joint === 'LeftLeg') rot.x = squat * 60;
|
||||
if (joint === 'RightLeg') rot.x = squat * 60;
|
||||
if (joint === 'LeftFoot') rot.x = -squat * 25;
|
||||
if (joint === 'RightFoot') rot.x = -squat * 25;
|
||||
if (joint === 'Spine') rot.z = phase * 5;
|
||||
if (joint === 'Neck') rot.z = -phase * 3;
|
||||
values.push(rot.z, rot.x, rot.y);
|
||||
}
|
||||
if (values.length !== CHANNELS) throw new Error(`帧 ${f} 通道数错误:${values.length}`);
|
||||
lines.push(values.map((v) => v.toFixed(4)).join(' '));
|
||||
}
|
||||
|
||||
const text = `${HIERARCHY}Frames: ${FRAMES}\nFrame Time: ${FRAME_TIME.toFixed(7)}\n${lines.join('\n')}\n`;
|
||||
writeFileSync(outPath, text, 'utf8');
|
||||
console.log(`已生成 ${outPath}(${FRAMES} 帧,${CHANNELS} 通道,${text.length} 字节)`);
|
||||
Reference in New Issue
Block a user