Files
zhengte.babylonjs-sdk/mock-arkit-server.js
2026-01-05 10:14:33 +08:00

114 lines
4.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 模拟 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(' -> '));