114 lines
4.3 KiB
JavaScript
114 lines
4.3 KiB
JavaScript
/**
|
||
* 模拟 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(' -> '));
|