init
This commit is contained in:
113
mock-arkit-server.js
Normal file
113
mock-arkit-server.js
Normal 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(' -> '));
|
||||
Reference in New Issue
Block a user