This commit is contained in:
yinsx
2026-01-05 09:41:23 +08:00
commit 2ad9f27457
30 changed files with 3044 additions and 0 deletions

113
mock-arkit-server.js Normal file
View File

@ -0,0 +1,113 @@
/**
* 模拟 ARKit 数据的 WebSocket 服务器
* 运行: node mock-arkit-server.js
*/
const WebSocket = require('ws');
const PORT = 8765;
const wss = new WebSocket.Server({ port: PORT });
// ARKit 52 个 blendShape 完整列表
const ARKIT_BLENDSHAPES = [
'eyeBlinkLeft', 'eyeLookDownLeft', 'eyeLookInLeft', 'eyeLookOutLeft', 'eyeLookUpLeft', 'eyeSquintLeft', 'eyeWideLeft',
'eyeBlinkRight', 'eyeLookDownRight', 'eyeLookInRight', 'eyeLookOutRight', 'eyeLookUpRight', 'eyeSquintRight', 'eyeWideRight',
'jawForward', 'jawLeft', 'jawRight', 'jawOpen',
'mouthClose', 'mouthFunnel', 'mouthPucker', 'mouthLeft', 'mouthRight',
'mouthSmileLeft', 'mouthSmileRight', 'mouthFrownLeft', 'mouthFrownRight',
'mouthDimpleLeft', 'mouthDimpleRight', 'mouthStretchLeft', 'mouthStretchRight',
'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',
'mouthPressLeft', 'mouthPressRight', 'mouthLowerDownLeft', 'mouthLowerDownRight',
'mouthUpperUpLeft', 'mouthUpperUpRight',
'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',
'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',
'noseSneerLeft', 'noseSneerRight', 'tongueOut'
];
// 表情预设
const expressions = [
{ name: '微笑', data: { mouthSmileLeft: 0.8, mouthSmileRight: 0.8, cheekSquintLeft: 0.3, cheekSquintRight: 0.3 } },
{ name: '张嘴说话', data: { jawOpen: 0.5, mouthFunnel: 0.3 } },
{ name: '惊讶', data: { eyeWideLeft: 0.9, eyeWideRight: 0.9, jawOpen: 0.6, browInnerUp: 0.7 } },
{ name: '皱眉', data: { browDownLeft: 0.8, browDownRight: 0.8, eyeSquintLeft: 0.3, eyeSquintRight: 0.3 } },
{ name: '嘟嘴', data: { mouthPucker: 0.9, mouthFunnel: 0.4 } },
{ name: '吐舌', data: { tongueOut: 0.7, jawOpen: 0.4 } },
{ name: '生气', data: { noseSneerLeft: 0.7, noseSneerRight: 0.7, browDownLeft: 0.6, browDownRight: 0.6, jawOpen: 0.2 } },
{ name: '悲伤', data: { mouthFrownLeft: 0.7, mouthFrownRight: 0.7, browInnerUp: 0.5, eyeSquintLeft: 0.2, eyeSquintRight: 0.2 } },
{ name: '中性', data: {} },
];
let currentExprIndex = 0;
let transitionProgress = 0;
let blinkTimer = 0;
let isBlinking = false;
function lerp(a, b, t) {
return a + (b - a) * t;
}
function generateBlendShapes() {
const current = expressions[currentExprIndex].data;
const next = expressions[(currentExprIndex + 1) % expressions.length].data;
// 初始化所有 52 个 blendShape 为 0
const blendShapes = {};
ARKIT_BLENDSHAPES.forEach(name => blendShapes[name] = 0);
// 插值当前和下一个表情
for (const key of ARKIT_BLENDSHAPES) {
const currentVal = current[key] || 0;
const nextVal = next[key] || 0;
blendShapes[key] = lerp(currentVal, nextVal, transitionProgress);
}
// 自然眨眼每3-5秒眨一次
blinkTimer++;
if (!isBlinking && blinkTimer > 90 + Math.random() * 60) {
isBlinking = true;
blinkTimer = 0;
}
if (isBlinking) {
const blinkProgress = blinkTimer / 6;
if (blinkProgress < 1) {
blendShapes.eyeBlinkLeft = Math.sin(blinkProgress * Math.PI);
blendShapes.eyeBlinkRight = Math.sin(blinkProgress * Math.PI);
} else {
isBlinking = false;
blinkTimer = 0;
}
}
// 添加微小的随机抖动(更自然)
blendShapes.jawOpen += (Math.random() - 0.5) * 0.02;
blendShapes.browInnerUp += (Math.random() - 0.5) * 0.01;
// 表情过渡
transitionProgress += 0.015;
if (transitionProgress >= 1) {
transitionProgress = 0;
currentExprIndex = (currentExprIndex + 1) % expressions.length;
console.log(`切换到表情: ${expressions[currentExprIndex].name}`);
}
return blendShapes;
}
wss.on('connection', (ws) => {
console.log('客户端已连接');
const interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
const data = generateBlendShapes();
ws.send(JSON.stringify(data));
}
}, 33); // ~30fps
ws.on('close', () => {
console.log('客户端断开');
clearInterval(interval);
});
});
console.log(`ARKit 模拟服务器运行在 ws://localhost:${PORT}`);
console.log('表情循环: ' + expressions.map(e => e.name).join(' -> '));