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