125 lines
3.6 KiB
TypeScript
125 lines
3.6 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
||
|
||
import { parseMocapPacket, resolveMocapPalmCenter } from './useMocapInput';
|
||
|
||
describe('resolveMocapPalmCenter', () => {
|
||
test('优先用手腕和四个 MCP 点加权计算掌心派生点', () => {
|
||
const center = resolveMocapPalmCenter([
|
||
{ name: 'wrist', x: 0.1, y: 0.2 },
|
||
{ name: 'index_mcp', x: 0.3, y: 0.4 },
|
||
{ name: 'middle_mcp', x: 0.5, y: 0.6 },
|
||
{ name: 'ring_mcp', x: 0.7, y: 0.8 },
|
||
{ name: 'pinky_mcp', x: 0.9, y: 1 },
|
||
{ name: 'index_finger_tip', x: 1, y: 1 },
|
||
]);
|
||
|
||
expect(center?.x).toBeCloseTo(0.44);
|
||
expect(center?.y).toBeCloseTo(0.54);
|
||
});
|
||
|
||
test('可用掌心点少于三个时不返回掌心坐标', () => {
|
||
expect(
|
||
resolveMocapPalmCenter([
|
||
{ name: 'wrist', x: 0.1, y: 0.2 },
|
||
{ name: 'index_mcp', x: 0.3, y: 0.4 },
|
||
]),
|
||
).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('parseMocapPacket', () => {
|
||
test('解析手部数据时优先把 primaryHand 定位到掌心而不是腕部或指尖', () => {
|
||
const command = parseMocapPacket({
|
||
hands: [
|
||
{
|
||
state: 'open_palm',
|
||
x: 0.01,
|
||
y: 0.02,
|
||
landmarks: [
|
||
{ name: 'wrist', x: 0.1, y: 0.2 },
|
||
{ name: 'index_mcp', x: 0.3, y: 0.4 },
|
||
{ name: 'middle_mcp', x: 0.5, y: 0.6 },
|
||
{ name: 'ring_mcp', x: 0.7, y: 0.8 },
|
||
{ name: 'pinky_mcp', x: 0.9, y: 1 },
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(command.primaryHand?.x).toBeCloseTo(0.44);
|
||
expect(command.primaryHand?.y).toBeCloseTo(0.54);
|
||
expect(command.primaryHand).toEqual(
|
||
expect.objectContaining({
|
||
state: 'open_palm',
|
||
source: 'palm_center',
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('缺少足够掌心关键点时退回 wrist landmark,再退回 hand 直出坐标', () => {
|
||
const landmarkFallback = parseMocapPacket({
|
||
hands: [
|
||
{
|
||
state: 'grab',
|
||
x: 0.9,
|
||
y: 0.8,
|
||
landmarks: [{ name: 'wrist', x: 0.25, y: 0.75 }],
|
||
},
|
||
],
|
||
});
|
||
expect(landmarkFallback.primaryHand).toEqual(
|
||
expect.objectContaining({x: 0.25, y: 0.75, source: 'landmark'}),
|
||
);
|
||
|
||
const directFallback = parseMocapPacket({
|
||
hands: [{ state: 'grab', x: 0.9, y: 0.8 }],
|
||
});
|
||
expect(directFallback.primaryHand).toEqual(
|
||
expect.objectContaining({x: 0.9, y: 0.8, source: 'direct'}),
|
||
);
|
||
});
|
||
|
||
test('解析 mocap frame 的身体中心和左右手来源', () => {
|
||
const command = parseMocapPacket({
|
||
schema_version: '1.0',
|
||
stream: { type: 'mocap.frame' },
|
||
general: {
|
||
body: {
|
||
center_norm: [0.34, 0.58],
|
||
},
|
||
},
|
||
actions: [{ gesture: 'wave-left-hand' }],
|
||
hands: [
|
||
{
|
||
label: 'Left',
|
||
state: 'open_palm',
|
||
landmarks: [
|
||
{ name: 'wrist', x: 0.21, y: 0.31 },
|
||
{ name: 'index_mcp', x: 0.25, y: 0.33 },
|
||
{ name: 'middle_mcp', x: 0.27, y: 0.34 },
|
||
{ name: 'ring_mcp', x: 0.28, y: 0.35 },
|
||
{ name: 'pinky_mcp', x: 0.29, y: 0.36 },
|
||
],
|
||
},
|
||
{
|
||
label: 'Right',
|
||
state: 'unknown',
|
||
x: 0.72,
|
||
y: 0.32,
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(command.bodyCenter).toEqual({x: 0.34, y: 0.58});
|
||
expect(command.actions).toEqual(
|
||
expect.arrayContaining(['wave_left_hand', 'open_palm']),
|
||
);
|
||
expect(command.leftHand).toEqual(
|
||
expect.objectContaining({side: 'left', source: 'palm_center'}),
|
||
);
|
||
expect(command.rightHand).toEqual(
|
||
expect.objectContaining({x: 0.72, y: 0.32, side: 'right'}),
|
||
);
|
||
});
|
||
});
|