feat(web): T-WEB-006 BVH/FBX → MediaPipe33 关键点预处理

This commit is contained in:
2026-09-14 19:00:55 +08:00
parent 73e793318e
commit a2e3282603
12 changed files with 1799 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
export type Vec3 = [number, number, number];
/** 3×3 行主序矩阵 */
export type Mat3 = [number, number, number, number, number, number, number, number, number];
export const IDENTITY3: Mat3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];
export function vAdd(a: Vec3, b: Vec3): Vec3 {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
}
export function vSub(a: Vec3, b: Vec3): Vec3 {
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}
export function vScale(a: Vec3, s: number): Vec3 {
return [a[0] * s, a[1] * s, a[2] * s];
}
export function vMid(a: Vec3, b: Vec3): Vec3 {
return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
}
export function vLen(a: Vec3): number {
return Math.hypot(a[0], a[1], a[2]);
}
export function vNorm(a: Vec3): Vec3 {
const len = vLen(a);
if (len < 1e-9) return [0, 0, 0];
return [a[0] / len, a[1] / len, a[2] / len];
}
export function vDot(a: Vec3, b: Vec3): number {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
/** 向量 ba 与 bc 夹角的余弦,落在 [-1, 1] —— 契约 skeleton-topology.md §5 */
export function angleCos(a: Vec3, b: Vec3, c: Vec3): number {
const ba = vSub(a, b);
const bc = vSub(c, b);
const denom = vLen(ba) * vLen(bc);
if (denom < 1e-9) return 0;
return Math.max(-1, Math.min(1, vDot(ba, bc) / denom));
}
export function matMul(a: Mat3, b: Mat3): Mat3 {
const out = new Array<number>(9);
for (let r = 0; r < 3; r += 1) {
for (let c = 0; c < 3; c += 1) {
out[r * 3 + c] = a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c];
}
}
return out as Mat3;
}
export function matVec(m: Mat3, v: Vec3): Vec3 {
return [
m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
m[6] * v[0] + m[7] * v[1] + m[8] * v[2],
];
}
const DEG2RAD = Math.PI / 180;
/** 绕单轴旋转矩阵,axis ∈ {X, Y, Z} */
export function rotationMatrix(axis: string, degrees: number): Mat3 {
const rad = degrees * DEG2RAD;
const c = Math.cos(rad);
const s = Math.sin(rad);
switch (axis.toUpperCase()) {
case 'X':
return [1, 0, 0, 0, c, -s, 0, s, c];
case 'Y':
return [c, 0, s, 0, 1, 0, -s, 0, c];
case 'Z':
return [c, -s, 0, s, c, 0, 0, 0, 1];
default:
return IDENTITY3;
}
}
/**
* 由通道序列合成局部旋转矩阵。
* BVH 约定:按通道声明顺序依次左乘,即 R = R1 × R2 × R3(内旋)。
*/
export function composeRotation(channels: ReadonlyArray<{ axis: string; value: number }>): Mat3 {
let out: Mat3 = IDENTITY3;
for (const { axis, value } of channels) {
out = matMul(out, rotationMatrix(axis, value));
}
return out;
}
/** 世界变换:旋转 + 平移 */
export interface Transform {
r: Mat3;
t: Vec3;
}
export const IDENTITY_TRANSFORM: Transform = { r: IDENTITY3, t: [0, 0, 0] };
export function applyTransform(parent: Transform, offset: Vec3, localR: Mat3, extraT?: Vec3): Transform {
return {
r: matMul(parent.r, localR),
t: vAdd(parent.t, matVec(parent.r, vAdd(offset, extraT ?? [0, 0, 0]))),
};
}