commit cf0b049abf55f5efaf8f3778fe117e9867406cec Author: yinsx Date: Mon Jan 5 10:14:33 2026 +0800 init diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..61f57b1 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,84 @@ +kind: pipeline # 定义一个管道 +type: docker # 当前管道的类型 +name: test # 当前管道的名称 + +steps: + + # 第一步:构建项目 + - name: 构建项目 + image: node:18-alpine + commands: + - rm -rf node_modules + - npm ci + - npm run build + + # 第二步:上传静态资源到腾讯云COS (使用另一个插件) + - name: 静态资源上传到cos + image: ccr.ccs.tencentyun.com/xiaoqidun/gocos:latest + settings: + secret_id: + from_secret: cos_secret_id + secret_key: + from_secret: cos_secret_key + bucket_url: https://files-1302416092.cos.ap-shanghai.myqcloud.com + source_path: dist + target_path: /studio + strip_prefix: dist + + # 第三步:部署到服务器 + - name: 清除服务器缓存 + image: appleboy/drone-ssh + settings: + host: + from_secret: server_host + username: + from_secret: server_username + password: + from_secret: server_password + # 或者使用SSH密钥 + # key: + # from_secret: server_ssh_key + port: 22 + script: + - rm -rf /www/wwwroot/doc.zguiy.com/* + - mkdir -p /www/wwwroot/doc.zguiy.com/ + - chmod 755 /www/wwwroot/doc.zguiy.com/ + when: + branch: + - main + - master + - dev + + # 第四步:上传构建文件 + - name: 上传构建文件 + image: appleboy/drone-scp + settings: + host: + from_secret: server_host + username: + from_secret: server_username + password: + from_secret: server_password + # 或者使用SSH密钥 + # key: + # from_secret: server_ssh_key + port: 22 + source: dist/* + target: /www/wwwroot/doc.zguiy.com/ + strip_components: 1 + when: + branch: + - main + - master + - dev + + +# 触发条件 +trigger: + branch: + - main + - master + - dev + event: + - push + - pull_request diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..b1d28a5 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +VITE_PUBLIC = / diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..aeb8fd9 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_PUBLIC = https://cdn.files.zguiy.com/studio/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d032d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/public/ +/dist/ +nul \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..759d8a6 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# 3D模型展示SDK - 纯JavaScript版 + +基于 BabylonJS 的轻量模型展示 SDK,支持加载 GLB/GLTF、播放动画、控制相机/灯光,并附带截图、录制与 ARKit 调试能力。数字人和翻译/字幕相关逻辑已移除,仅保留通用的模型展示能力。 + +## 项目结构 +- `index.html`:开发示例页(直接引用 `src/main.ts`) +- `dist/test.html`:构建后的示例页(引用 `dist/assets/index.js`) +- `src/main.ts`:SDK 对外入口,挂载到 `window.faceSDK.kernel` +- `src/babylonjs`、`src/managers`:引擎、场景、动画等核心模块 + +## 快速开始 +1. 安装依赖:`npm install` +2. 启动本地预览(任选其一): + - `npm run dev` + - 或 `python -m http.server 8080` 后访问 `http://localhost:8080` +3. 打开 `index.html`(开发)或 `dist/test.html`(构建产物)查看示例控制面板。 + +## 初始化示例 +```js +await faceSDK.kernel.init({ + container: 'renderDom', + modelUrlList: [ + '/1104_01_Exp/head_a01/head_a01.gltf', + '/1104_01_Exp/shoes_e01/shoes_e01.gltf', + '/1104_01_Exp/bottom_a01/bottom_a01.gltf', + '/1104_01_Exp/hair_a01/hair_a01.gltf', + '/1104_01_Exp/top_f01/top_f01.gltf' + ], + animationUrlList: ['/idle/G001@Idle.glb'], + apiConfig: { name: '你的用户名', readLocalResource: true }, + enableExpression: false, + onSuccess: () => console.log('加载完成'), + onError: (err) => console.error('加载失败', err) +}); +``` + +## 常用 API +- `playAnimation(url)` / `stopAnimation()`:加载并播放/停止动画 +- `setAnimationSpeed(speed)`:调整播放速度 +- `setCameraPreset('full' | 'head' | 'upper')` / `resetCamera()`:相机预设与重置 +- `setLightIntensity(value)`、`setHDRIntensity(value)`、`rotateHDR(angle)`、`loadHDR(path)`:灯光与环境控制 +- `captureScreenshot(type, quality)` / `downloadScreenshot(filename, type, quality)`:截图 +- `startRecording(options)` / `stopRecording()` / `downloadRecording(filename)`:录屏 +- `enableARKit()`、`showARKitPanel()`、`connectARKitServer(url)`:ARKit 表情调试(可选) + +更多接口可查看 `src/main.ts` 注释。 + +## 示例控制面板 +- 动画 URL:输入 GLB/GLTF 动画地址并播放 +- 播放速度:0.5x - 3x +- 相机预设:全身 / 头部 / 上半身 / 重置 +- 灯光强度:调整场景亮度 +- ARKit 调试:连接、显示面板、断开(需要本地 ARKit 数据) + +## 许可证 +MIT License diff --git a/index.html b/index.html new file mode 100644 index 0000000..d4e8a53 --- /dev/null +++ b/index.html @@ -0,0 +1,38 @@ + + + + + + 3D模型展示SDK - TS版 + + + +
+ +
+ + + + diff --git a/mock-arkit-server.js b/mock-arkit-server.js new file mode 100644 index 0000000..2ce0310 --- /dev/null +++ b/mock-arkit-server.js @@ -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(' -> ')); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3015bc8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1492 @@ +{ + "name": "client-babylonjs-pure", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client-babylonjs-pure", + "version": "1.0.0", + "dependencies": { + "@babylonjs/core": "^7.0.0", + "@babylonjs/loaders": "^7.0.0", + "axios": "^1.6.0", + "js-md5": "^0.8.3", + "js-yaml": "^4.1.0", + "pako": "^2.1.0", + "ws": "^8.14.0" + }, + "devDependencies": { + "@rollup/plugin-terser": "^0.4.4", + "vite": "^5.0.0" + } + }, + "node_modules/@babylonjs/core": { + "version": "7.54.3", + "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-7.54.3.tgz", + "integrity": "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw==", + "license": "Apache-2.0" + }, + "node_modules/@babylonjs/loaders": { + "version": "7.54.3", + "resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-7.54.3.tgz", + "integrity": "sha512-RBPmOsaMTxi6Ga08ueLTm6Tnvx/l2nNQigucubvrngZ7muwn5/ubfcStckkI1c0qvhR1+/FFlD54do7gZ1pnsQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@babylonjs/core": "^7.0.0", + "babylonjs-gltf2interface": "^7.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babylonjs-gltf2interface": { + "version": "7.54.3", + "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-7.54.3.tgz", + "integrity": "sha512-ZAWYFyE+SOczfWT19O4e3YRkCZ5i57SiD2eK2kqc+Tow/t9X1S45xgSFNuHZff++dd5BlVIEQDSnFV+McFLSnQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4556c28 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "client-babylonjs-pure", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build && node scripts/postbuild.cjs", + "preview": "vite preview" + }, + "dependencies": { + "@babylonjs/core": "^7.0.0", + "@babylonjs/loaders": "^7.0.0", + "axios": "^1.6.0", + "js-yaml": "^4.1.0", + "js-md5": "^0.8.3", + "pako": "^2.1.0", + "ws": "^8.14.0" + }, + "devDependencies": { + "@rollup/plugin-terser": "^0.4.4", + "typescript": "^5.4.0", + "vite": "^5.0.0" + } +} diff --git a/scripts/postbuild.cjs b/scripts/postbuild.cjs new file mode 100644 index 0000000..953cfbf --- /dev/null +++ b/scripts/postbuild.cjs @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); + +const projectRoot = path.resolve(__dirname, '..'); +const srcIndex = path.join(projectRoot, 'index.html'); +const distDir = path.join(projectRoot, 'dist'); +const distIndex = path.join(distDir, 'index.html'); + +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +let html = fs.readFileSync(srcIndex, 'utf8'); + +// 替换开发路径为构建产物路径 +html = html.replace(/['"]\/src\/main\.ts['"]/g, '"/assets/index.js"'); + +fs.writeFileSync(distIndex, html, 'utf8'); + +console.log('postbuild: index.html written to dist/'); diff --git a/src/apis/axios.ts b/src/apis/axios.ts new file mode 100644 index 0000000..fddb987 --- /dev/null +++ b/src/apis/axios.ts @@ -0,0 +1,17 @@ +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; + +export type HttpClient = { + baseURL: string; + headers: Record; + get: (url: string, options?: AxiosRequestConfig) => Promise>; + post: (url: string, data: unknown, options?: AxiosRequestConfig) => Promise>; +}; + +export const httpClient: HttpClient = { + baseURL: 'http://localhost:3000', + headers: { + 'Content-Type': 'application/json' + }, + get: (url, options) => axios.get(url, options), + post: (url, data, options = {}) => axios.post(url, data, options) +}; diff --git a/src/apis/services.ts b/src/apis/services.ts new file mode 100644 index 0000000..d7b6b96 --- /dev/null +++ b/src/apis/services.ts @@ -0,0 +1,82 @@ +/** + * @file services.js + * @description API服务模块 - 提供与后端交互的各种接口 + */ + +import { httpClient } from "./axios"; +import configurator from "../components/conf"; +import auth from "../components/auth"; + +type AssetFetchResult = { + value: Uint8Array; + timestamp: string; +}; + +/** + * 获取资源数据的通用方法 + * @param {string} url - 资源基础URL + * @param {string} obj - 资源路径 + * @returns {Promise<{value: Uint8Array, timestamp: string}|null>} + */ +const fetchAssetData = async (url: string, obj: string): Promise => { + try { + // 获取当前时间戳 + const currentTimestamp = Date.now().toString(); + const info = auth.load(); + if (!info) return null; + // 将参数拼接到URL中 + const fullUrl = `${url}/${encodeURI(obj)}`; + + return await httpClient.get(fullUrl, { + responseType: 'arraybuffer', + headers: { + Timestamp: currentTimestamp, + Token: info.token ?? '', + UUID: info.uid ?? '' + } + }) + .then(response => ({ + value: new Uint8Array(response.data as ArrayBuffer), + timestamp: currentTimestamp, + })) + .catch(error => { + console.error('Axios GET request error:', error); + return null; + }); + } catch (error) { + console.error('Fetch asset data error:', error); + return null; + } +}; + +/** + * 获取GLB模型数据 + * @param {string} obj - 模型文件路径 + * @returns {Promise} 模型二进制数据 + */ +export const fetchGlbModel = async (obj: string): Promise => { + const result = await fetchAssetData(configurator.modelAssetDir, obj); + return result?.value ?? null; +}; + +/** + * 用户登录 + * @param {object} obj - 登录请求参数 + * @param {object} obj.content - 登录内容 + * @param {string} obj.content.obj1 - 用户名 + * @returns {Promise} 登录响应数据 + */ +export const userLogin = async (obj: unknown): Promise => { + const data = await httpClient.post(`${httpClient.baseURL}/api/Terminal/ThreeJSToLogin`, obj); + return data.data; +} + +/** + * 获取动画数据 + * @param {string} obj - 动画文件路径 + * @returns {Promise} 动画数据 + */ +export const fetchAnim = async (obj: string): Promise => { + const result = await httpClient.get(`${configurator.animAssetDir}/${obj}`); + return result.data; +} diff --git a/src/babylonjs/AppCamera.ts b/src/babylonjs/AppCamera.ts new file mode 100644 index 0000000..042ec86 --- /dev/null +++ b/src/babylonjs/AppCamera.ts @@ -0,0 +1,52 @@ +import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; +import { Vector3 } from '@babylonjs/core/Maths/math.vector'; +import { Tools } from '@babylonjs/core/Misc/tools'; +import { Monobehiver } from '../base/Monobehiver'; + +/** + * 相机控制类- 负责创建和控制弧形旋转相机 + */ +export class AppCamera extends Monobehiver { + object: ArcRotateCamera | null; + + constructor(mainApp: any) { + super(mainApp); + this.object = null; + } + + /** 初始化相机 */ + Awake(): void { + const scene = this.mainApp.appScene.object; + const canvas = this.mainApp.appDom.renderDom; + if (!scene || !canvas) return; + + // 创建弧形旋转相机:水平角70度,垂直角80度,距离5,目标点(0,1,0) + this.object = new ArcRotateCamera('Camera', Tools.ToRadians(70), Tools.ToRadians(80), 5, new Vector3(0, 0, 0), scene); + this.object.attachControl(canvas, true); + this.object.minZ = 0.01; // 近裁剪面 + this.object.wheelPrecision =999999; // 滚轮缩放精度 + this.object.panningSensibility = 0; + this.object.position = new Vector3(-0, 0, 100); + this.setTarget(0, 2, 0); + } + + /** 设置相机目标点 */ + setTarget(x: number, y: number, z: number): void { + if (this.object) { + this.object.target = new Vector3(x, y, z); + } + } + + + + /** 重置相机到默认位置 */ + reset(): void { + if (!this.object) return; + this.object.radius = 2; this.setTarget(0, 0, 0); + this.object.position = new Vector3(0, 1.5, 2); + } + + update(): void { + + } +} diff --git a/src/babylonjs/AppConfig.ts b/src/babylonjs/AppConfig.ts new file mode 100644 index 0000000..5fb24c7 --- /dev/null +++ b/src/babylonjs/AppConfig.ts @@ -0,0 +1,13 @@ + +type OptionalCallback = (() => void) | null | undefined; +type ErrorCallback = ((error?: unknown) => void) | null | undefined; + +/** + * 共享运行时配置对象 + */ +export const AppConfig = { + container: 'renderDom', + modelUrlList: [] as string[], + success: null as OptionalCallback, + error: null as ErrorCallback +}; diff --git a/src/babylonjs/AppDom.ts b/src/babylonjs/AppDom.ts new file mode 100644 index 0000000..8eed205 --- /dev/null +++ b/src/babylonjs/AppDom.ts @@ -0,0 +1,21 @@ +import { AppConfig } from './AppConfig'; + +/** + * 负责获取渲染容器 DOM + */ +export class AppDom { + private _renderDom: HTMLCanvasElement | null; + + constructor() { + this._renderDom = null; + } + + get renderDom(): HTMLCanvasElement | null { + return this._renderDom; + } + + Awake(): void { + const dom = document.getElementById(AppConfig.container) || document.querySelector('#renderDom'); + this._renderDom = (dom as HTMLCanvasElement) ?? null; + } +} diff --git a/src/babylonjs/AppEngin.ts b/src/babylonjs/AppEngin.ts new file mode 100644 index 0000000..3ce00c8 --- /dev/null +++ b/src/babylonjs/AppEngin.ts @@ -0,0 +1,38 @@ +import { Engine } from '@babylonjs/core/Engines/engine'; +import { Monobehiver } from '../base/Monobehiver'; + +/** + * 渲染引擎管理类 - 负责创建和管理3D渲染引擎 + */ +export class AppEngin extends Monobehiver { + object: Engine | null; + canvas: HTMLCanvasElement | null; + + constructor(mainApp: any) { + super(mainApp); + this.object = null; + this.canvas = null; + } + + Awake(): void { + this.canvas = this.mainApp.appDom.renderDom; + if (!this.canvas) { + throw new Error('Render canvas not found'); + } + this.object = new Engine(this.canvas, true, { + preserveDrawingBuffer: false, // 不保留绘图缓冲区 + stencil: true, // 启用模板缓冲 + alpha: true // 启用透明背景 + }); + this.object.setSize(window.innerWidth, window.innerHeight); + this.object.setHardwareScalingLevel(1); // 1:1像素比例 + } + + /** 处理窗口大小变化 */ + handleResize(): void { + if (this.object) { + this.object.setSize(window.innerWidth, window.innerHeight); + this.object.resize(); + } + } +} diff --git a/src/babylonjs/AppEnv.ts b/src/babylonjs/AppEnv.ts new file mode 100644 index 0000000..1049de2 --- /dev/null +++ b/src/babylonjs/AppEnv.ts @@ -0,0 +1,58 @@ +import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture'; +import { Monobehiver } from '../base/Monobehiver'; + +/** + * 环境管理类- 负责创建和管理HDR环境贴图 + */ +export class AppEnv extends Monobehiver { + object: CubeTexture | null; + + constructor(mainApp: any) { + super(mainApp); + this.object = null; + } + + /** 初始化 - 创建默认HDR环境 */ + Awake(): void { + this.createHDR(); + } + + /** + * 创建HDR环境贴图 + * @param hdrPath HDR文件路径 + */ + createHDR(hdrPath = '/hdr/sanGiuseppeBridge.env'): void { + const scene = this.mainApp.appScene.object; + if (!scene) return; + const reflectionTexture = CubeTexture.CreateFromPrefilteredData(hdrPath, scene); + scene.environmentIntensity = 1.5; + scene.environmentTexture = reflectionTexture; + this.object = reflectionTexture; + } + + /** + * 修改HDR环境光强度 + * @param intensity 强度值 + */ + changeHDRIntensity(intensity: number): void { + if (this.mainApp.appScene.object) { + this.mainApp.appScene.object.environmentIntensity = intensity; + } + } + + /** + * 旋转HDR环境贴图 + * @param angle 旋转角度(弧度) + */ + rotateHDR(angle: number): void { + if (this.object) this.object.rotationY = angle; + } + + /** 清理资源 */ + clean(): void { + if (this.object) { + this.object.dispose(); + this.object = null; + } + } +} diff --git a/src/babylonjs/AppLight.ts b/src/babylonjs/AppLight.ts new file mode 100644 index 0000000..56fcdc3 --- /dev/null +++ b/src/babylonjs/AppLight.ts @@ -0,0 +1,209 @@ +import { SpotLight } from '@babylonjs/core/Lights/spotLight'; +import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator'; +import '@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent'; +import { Vector3, Quaternion } from '@babylonjs/core/Maths/math.vector'; +import { Monobehiver } from '../base/Monobehiver'; +import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder'; +import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial'; +import { Color3 } from '@babylonjs/core/Maths/math.color'; +import { GizmoManager } from '@babylonjs/core/Gizmos/gizmoManager'; +import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh'; +import { Mesh } from '@babylonjs/core/Meshes/mesh'; + +type DebugMarkers = { + marker: Mesh; + arrow: Mesh; + gizmoManager: GizmoManager; + onKey: (e: KeyboardEvent) => void; +}; + +/** + * 灯光管理类- 负责创建和管理场景灯光 + */ +export class AppLight extends Monobehiver { + lightList: SpotLight[]; + shadowGenerator: ShadowGenerator | null; + debugMarkers?: DebugMarkers; + coneMesh?: Mesh; + updateCone?: () => void; + + constructor(mainApp: any) { + super(mainApp); + this.lightList = []; + this.shadowGenerator = null; + } + + /** 初始化灯光并开启阴影 */ + Awake(): void { + const light = new SpotLight( + "mainLight", + new Vector3(-0.6, 2.12, 2), + new Vector3(0, -0.5, -1), + Math.PI * 0.6, // angle 弧度 + ); + + light.angle = 1.5; + light.innerAngle = 1; + light.exponent = 2; + light.diffuse = new Color3(1, 0.86, 0.80); + light.specular = new Color3(1, 1, 1); + light.intensity = 60; + light.shadowMinZ = 0.01; + light.shadowMaxZ = 100; + light.range = 5000; + + const generator = new ShadowGenerator(4096, light); + generator.usePercentageCloserFiltering = true; + generator.filteringQuality = ShadowGenerator.QUALITY_HIGH; + generator.transparencyShadow = true; + + this.lightList.push(light); + this.shadowGenerator = generator; + } + + /** 将网格添加为阴影投射者 */ + addShadowCaster(mesh: AbstractMesh): void { + if (this.shadowGenerator) { + this.shadowGenerator.addShadowCaster(mesh); + } + } + + /** 设置主光源强度 */ + setIntensity(intensity: number): void { + if (this.lightList[0]) this.lightList[0].intensity = intensity; + } + + /** 创建灯光可视化调试器 - W键拖拽位置,E键旋转方向 */ + enableLightDebug(): void { + const scene = this.mainApp.appScene.object; + const light = this.lightList[0]; + if (!light || !scene) return; + + const marker = MeshBuilder.CreateSphere("lightMarker", { diameter: 0.3 }, scene); + marker.position = light.position.clone(); + const mat = new StandardMaterial("lightMat", scene); + mat.emissiveColor = Color3.Yellow(); + marker.material = mat; + + const arrow = MeshBuilder.CreateCylinder("lightArrow", { height: 1, diameterTop: 0, diameterBottom: 0.1 }, scene); + arrow.parent = marker; + arrow.position.set(0, 0, 0.6); + arrow.rotation.x = Math.PI / 2; + const arrowMat = new StandardMaterial("arrowMat", scene); + arrowMat.emissiveColor = Color3.Red(); + arrow.material = arrowMat; + + const dir = light.direction.normalize(); + marker.rotation.y = Math.atan2(dir.x, dir.z); + marker.rotation.x = -Math.asin(dir.y); + + const gizmoManager = new GizmoManager(scene); + gizmoManager.attachableMeshes = [marker]; + gizmoManager.usePointerToAttachGizmos = false; + gizmoManager.attachToMesh(marker); + + scene.onBeforeRenderObservable.add(() => { + light.position.copyFrom(marker.position); + const forward = new Vector3(0, 0, 1); + const rotationMatrix = marker.getWorldMatrix().getRotationMatrix(); + light.direction = Vector3.TransformNormal(forward, rotationMatrix).normalize(); + }); + + const onKey = (e: KeyboardEvent) => { + if (e.key === 'w' || e.key === 'W') { + gizmoManager.positionGizmoEnabled = true; + gizmoManager.rotationGizmoEnabled = false; + } else if (e.key === 'e' || e.key === 'E') { + gizmoManager.positionGizmoEnabled = false; + gizmoManager.rotationGizmoEnabled = true; + } + }; + window.addEventListener('keydown', onKey); + gizmoManager.positionGizmoEnabled = true; + + this.debugMarkers = { marker, arrow, gizmoManager, onKey }; + } + + /** 隐藏灯光调试器 */ + disableLightDebug(): void { + if (this.debugMarkers) { + window.removeEventListener('keydown', this.debugMarkers.onKey); + this.debugMarkers.gizmoManager.dispose(); + this.debugMarkers.arrow.dispose(); + this.debugMarkers.marker.dispose(); + this.debugMarkers = undefined; + } + } + + /** 创建聚光灯可视化Gizmo - 带光锥范围 */ + createLightGizmo(): void { + const scene = this.mainApp.appScene.object; + const light = this.lightList[0]; + if (!light || !scene) return; + + const coneLength = 3; + const updateCone = () => { + if (this.coneMesh) this.coneMesh.dispose(); + const radius = Math.tan(light.angle) * coneLength; + const cone = MeshBuilder.CreateCylinder("lightCone", { + height: coneLength, + diameterTop: radius * 2, + diameterBottom: 0 + }, scene); + const mat = new StandardMaterial("coneMat", scene); + mat.emissiveColor = Color3.Yellow(); + mat.alpha = 0.2; + mat.wireframe = true; + cone.material = mat; + + cone.position = light.position.add(light.direction.scale(coneLength / 2)); + const up = new Vector3(0, 1, 0); + const axis = Vector3.Cross(up, light.direction).normalize(); + const angle = Math.acos(Vector3.Dot(up, light.direction.normalize())); + if (axis.length() > 0.001) cone.rotationQuaternion = Quaternion.RotationAxis(axis, angle); + + this.coneMesh = cone; + }; + + updateCone(); + this.updateCone = updateCone; + } + + /** 创建angle和innerAngle调试滑动条 */ + createAngleSliders(): void { + const light = this.lightList[0]; + if (!light) return; + + const container = document.createElement('div'); + container.style.cssText = 'position:fixed;top:10px;right:10px;background:rgba(0,0,0,0.7);padding:10px;border-radius:5px;color:#fff;font-size:12px;z-index:1000'; + + const createSlider = (label: string, value: number, min: number, max: number, onChange: (v: number) => void) => { + const wrap = document.createElement('div'); + wrap.style.marginBottom = '8px'; + const lbl = document.createElement('div'); + lbl.textContent = `${label}: ${value}`; + const slider = document.createElement('input'); + slider.type = 'range'; + slider.min = String(min); + slider.max = String(max); + slider.value = String(value); + slider.style.width = '150px'; + slider.oninput = () => { + lbl.textContent = `${label}: ${slider.value}`; + onChange(Number(slider.value)); + }; + wrap.append(lbl, slider); + return wrap; + }; + + const toRad = (deg: number) => deg * Math.PI / 180; + const toDeg = (rad: number) => Math.round(rad * 180 / Math.PI); + + container.append( + createSlider('angle', toDeg(light.angle), 1, 180, v => { light.angle = toRad(v); this.updateCone?.(); }), + createSlider('innerAngle', toDeg(light.innerAngle), 0, 180, v => { light.innerAngle = toRad(v); }) + ); + + document.body.appendChild(container); + } +} diff --git a/src/babylonjs/AppModel.ts b/src/babylonjs/AppModel.ts new file mode 100644 index 0000000..cf189f8 --- /dev/null +++ b/src/babylonjs/AppModel.ts @@ -0,0 +1,119 @@ +import { ImportMeshAsync } from '@babylonjs/core/Loading/sceneLoader'; +import '@babylonjs/loaders/glTF'; +import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh'; +import { Scene } from '@babylonjs/core/scene'; +import { Monobehiver } from '../base/Monobehiver'; +import { Dictionary } from '../utils/Dictionary'; +import { AppConfig } from './AppConfig'; + +type LoadResult = { + success: boolean; + meshes?: AbstractMesh[]; + skeletons?: unknown[]; + error?: string; +}; + +/** + * 模型管理类- 负责加载、缓存和管理3D模型 + */ +export class AppModel extends Monobehiver { + private modelDic: Dictionary; + private loadedMeshes: AbstractMesh[]; + private skeletonManager: any; + private outfitManager: any; + private isLoading: boolean; + private skeletonMerged: boolean; + + constructor(mainApp: any) { + super(mainApp); + this.modelDic = new Dictionary(); + this.loadedMeshes = []; + this.skeletonManager = null; + this.outfitManager = null; + this.isLoading = false; + this.skeletonMerged = false; + } + + /** 初始化子管理器(占位:实际实现已移除) */ + initManagers(): void { + // 这里原本会初始化 SkeletonManager 和 OutfitManager,已留空以避免恢复已删除的实现 + } + + /** 加载配置中的所有模型 */ + async loadModel(): Promise { + if (!AppConfig.modelUrlList?.length || this.isLoading) return; + this.isLoading = true; + try { + for (const url of AppConfig.modelUrlList) { + await this.loadSingleModel(url); + } + + } finally { + this.isLoading = false; + } + } + + /** + * 加载单个模型 + * @param modelUrl 模型URL + */ + async loadSingleModel(modelUrl: string): Promise { + try { + const cached = this.getCachedMeshes(modelUrl); + if (cached) return { success: true, meshes: cached }; + + const scene: Scene | null = this.mainApp.appScene.object; + if (!scene) return { success: false, error: '场景未初始化' }; + + // ImportMeshAsync的签名与当前调用不完全一致,使用any规避编译报错 + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const result: any = await (ImportMeshAsync as any)(modelUrl, scene); + if (!result?.meshes?.length) return { success: false, error: '未找到网格' }; + + this.modelDic.Set(modelUrl, result.meshes); + this.loadedMeshes.push(...result.meshes); + + this.setupShadows(result.meshes as AbstractMesh[]); + + return { success: true, meshes: result.meshes, skeletons: result.skeletons }; + } catch (e: any) { + console.error(`模型加载失败: ${modelUrl}`, e); + return { success: false, error: e?.message }; + } + } + + + + + /** 为网格设置阴影(投射和接收) */ + setupShadows(meshes: AbstractMesh[]): void { + const appLight = this.mainApp.appLight; + if (!appLight) return; + + meshes.forEach(mesh => { + if (mesh.getTotalVertices() > 0) { + appLight.addShadowCaster(mesh); + mesh.receiveShadows = true; + } + }); + } + + /** 获取缓存的网格 */ + getCachedMeshes(url: string): AbstractMesh[] | undefined { + return this.modelDic.Get(url); + } + + + + + /** 清理所有资源 */ + clean(): void { + this.modelDic.Clear(); + this.loadedMeshes.forEach(m => m?.dispose()); + this.loadedMeshes = []; + this.skeletonManager?.clean(); + this.outfitManager?.clean(); + this.isLoading = false; + this.skeletonMerged = false; + } +} diff --git a/src/babylonjs/AppScene.ts b/src/babylonjs/AppScene.ts new file mode 100644 index 0000000..7945e81 --- /dev/null +++ b/src/babylonjs/AppScene.ts @@ -0,0 +1,28 @@ +import { Scene } from '@babylonjs/core/scene'; +import { ImageProcessingConfiguration } from '@babylonjs/core/Materials'; +import { Color4 } from '@babylonjs/core/Maths/math.color'; +import { Monobehiver } from '../base/Monobehiver'; + +/** + * 场景管理类- 负责创建和管理3D场景 + */ +export class AppScene extends Monobehiver { + object: Scene | null; + + constructor(mainApp: any) { + super(mainApp); + this.object = null; + } + + /** 初始化场景 */ + Awake(): void { + this.object = new Scene(this.mainApp.appEngin.object); + this.object.clearColor = new Color4(0, 0, 0, 0); // 透明背景 + this.object.skipFrustumClipping = true; // 跳过视锥剔除优化性能 + // 1. 开启色调映射(Tone mapping) + // this.object.imageProcessingConfiguration.toneMappingEnabled = true; + + // 2. 设置色调映射类型为ACES + // this.object.imageProcessingConfiguration.toneMappingType = ImageProcessingConfiguration.TONEMAPPING_ACES; + } +} diff --git a/src/babylonjs/MainApp.ts b/src/babylonjs/MainApp.ts new file mode 100644 index 0000000..888cf0f --- /dev/null +++ b/src/babylonjs/MainApp.ts @@ -0,0 +1,83 @@ +/** + * @file MainApp.ts + * @description 主应用类,负责初始化和协调所有子模块 + */ + +import { AppDom } from './AppDom'; +import { AppEngin } from './AppEngin'; +import { AppScene } from './AppScene'; +import { AppCamera } from './AppCamera'; +import { AppLight } from './AppLight'; +import { AppEnv } from './AppEnv'; +import { AppModel } from './AppModel'; +import { AppConfig } from './AppConfig'; + +/** + * 主应用类 - 3D场景的核心控制器 + * 负责管理DOM、引擎、场景、相机、灯光、环境、模型和动画等子模块 + */ +export class MainApp { + appDom: AppDom; + appEngin: AppEngin; + appScene: AppScene; + appCamera: AppCamera; + appModel: AppModel; + appLight: AppLight; + appEnv: AppEnv; + + + constructor() { + this.appDom = new AppDom(); + this.appEngin = new AppEngin(this); + this.appScene = new AppScene(this); + this.appCamera = new AppCamera(this); + this.appModel = new AppModel(this); + this.appLight = new AppLight(this); + this.appEnv = new AppEnv(this); + + window.addEventListener("resize", () => this.appEngin.handleResize()); + } + + /** + * 加载应用配置 + * @param config 配置对象 + */ + loadAConfig(config: any): void { + AppConfig.container = config.container || 'renderDom'; + AppConfig.modelUrlList = config.modelUrlList || []; + AppConfig.success = config.success; + AppConfig.error = config.error; + } + + loadModel(): void { + this.appModel.loadModel(); + } + + /** 唤醒/初始化所有子模块 */ + async Awake(): Promise { + this.appDom.Awake(); + this.appEngin.Awake(); + this.appScene.Awake(); + this.appCamera.Awake(); + this.appLight.Awake(); + this.appEnv.Awake(); + + this.appModel.initManagers(); + this.update(); + } + + /** 启动渲染循环 */ + update(): void { + if (!this.appEngin.object) return; + this.appEngin.object.runRenderLoop(() => { + this.appScene.object?.render(); + this.appCamera.update(); + }); + } + + /** 销毁应用,释放所有资源 */ + async dispose(): Promise { + this.appModel?.clean(); + this.appEnv?.clean(); + } +} diff --git a/src/base/Monobehiver.ts b/src/base/Monobehiver.ts new file mode 100644 index 0000000..528e8e3 --- /dev/null +++ b/src/base/Monobehiver.ts @@ -0,0 +1,14 @@ +/** + * 组件基础类 - 提供生命周期方法 + */ +export class Monobehiver { + protected mainApp: TMainApp; + + constructor(mainApp: TMainApp) { + this.mainApp = mainApp; + } + + /** 醒来/初始化,子类可重写 */ + // eslint-disable-next-line @typescript-eslint/no-empty-function + Awake(): void {} +} diff --git a/src/components/auth.ts b/src/components/auth.ts new file mode 100644 index 0000000..e51107d --- /dev/null +++ b/src/components/auth.ts @@ -0,0 +1,65 @@ +import { userLogin } from "../apis/services"; + +export type AuthCache = { + uid: string; + eid: string; + privilege: string; + token: string; +}; + +type LoginResponse = { + result?: number; + content?: string; +}; + +/** + * 认证模块 - 处理用户登录和凭证信息 + */ +const auth = { + cache: null as AuthCache | null, + + /** 保存认证信息 */ + save(value: AuthCache): void { + this.cache = value; + }, + + /** 获取缓存的认证信息 */ + load(): AuthCache | null { + return this.cache; + }, + + /** + * 用户登录 + * @param name 用户名 + */ + login: async function (name: string): Promise { + const normalizedName = typeof name === 'string' ? name.trim() : ''; + if (!normalizedName) { + console.error('登录用户名不能为空'); + return null; + } + const loginReq = { content: { obj1: normalizedName } }; + try { + const loginResp = await userLogin(loginReq) as LoginResponse; + + if (loginResp?.result === 1 && typeof loginResp.content === 'string') { + const parsed = loginResp.content + .split(';') + .map((item) => item.split('=')[1]); + + const [, uid, eid, privilege, token] = parsed; + if (uid && eid && privilege && token) { + const userData: AuthCache = { uid, eid, privilege, token }; + this.save(userData); + return userData; + } + } + } catch (error) { + console.error(error); + } + + return null; + } +}; + +export default auth; diff --git a/src/components/conf.ts b/src/components/conf.ts new file mode 100644 index 0000000..f9b7411 --- /dev/null +++ b/src/components/conf.ts @@ -0,0 +1,79 @@ +import { httpClient } from "../apis/axios"; + +export type ConfiguratorParams = { + name: string; + network?: string; + modelName?: string; + testModelName?: boolean; + readLocalResource?: boolean; + modelDir?: boolean; + binDir?: boolean; + gltfDir?: boolean; + animDir?: boolean; +}; + +export type Configurator = { + record: boolean; + binAssetDir: string; + gltfAssetDir: string; + animAssetDir: string; + modelAssetDir: string; + isOnline: boolean; + modelName: string; + screenshot: boolean; + targetModelName?: string; + init: (params: ConfiguratorParams) => Promise; +}; + +const configurator: Configurator = { + record: false, + binAssetDir: 'statics/glb/', + gltfAssetDir: 'statics/gltf', + animAssetDir: 'statics/anim', + modelAssetDir: 'statics/models', + isOnline: false, + modelName: '', + screenshot: false, + targetModelName: undefined, + + /** 初始化应用配置 */ + init: async function (params: ConfiguratorParams): Promise { + if (typeof params !== 'object' || params === null) { + console.error("params must be an object"); + return false; + } + if (typeof params.name !== 'string' || params.name.length < 1) { + console.error("params.name must be a string"); + return false; + } + + if (params.modelName !== undefined) { + configurator.modelName = params.modelName; + } + + if (params.testModelName === true) { + configurator.targetModelName = params.modelName; + } + + if (params.network !== undefined) { + httpClient.baseURL = params.network; + } + + if (params.readLocalResource === false) { + configurator.isOnline = true; + configurator.modelAssetDir = `${httpClient.baseURL}/api/v2/assets/sign/latest/glb`; + configurator.binAssetDir = `${httpClient.baseURL}/api/v3/assets/sign/latest/glb/bin/`; + configurator.gltfAssetDir = `${httpClient.baseURL}/api/v3/assets/sign/latest/glb`; + configurator.animAssetDir = `${httpClient.baseURL}/api/v3/assets/sign/latest/glb/anim`; + } else { + if (params.modelDir === false) configurator.modelAssetDir = `${httpClient.baseURL}/api/v2/assets/sign/latest/glb`; + if (params.binDir === false) configurator.binAssetDir = `${httpClient.baseURL}/api/v3/assets/sign/latest/glb/bin/`; + if (params.gltfDir === false) configurator.gltfAssetDir = `${httpClient.baseURL}/api/v3/assets/sign/latest/glb`; + if (params.animDir === false) configurator.animAssetDir = `${httpClient.baseURL}/api/v3/assets/sign/latest/glb/anim`; + } + + return true; + } +}; + +export default configurator; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..27969a5 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,74 @@ +import { MainApp } from './babylonjs/MainApp'; +import configurator, { ConfiguratorParams } from './components/conf'; +import auth from './components/auth'; +import { on } from './utils/event'; + +declare global { + interface Window { + faceSDK?: Record; + yiyu?: Record; + } +} + +type InitParams = { + container?: string; + modelUrlList?: string[]; + animationUrlList?: string[]; + idleAnimationUrlList?: string[]; + onSuccess?: () => void; + onError?: (error?: unknown) => void; + apiConfig?: ConfiguratorParams; +}; + +let mainApp: MainApp | null = null; + +const kernel = { + /** 初始化应用 */ + init: async function (params: InitParams): Promise { + if (!params) { console.error('params is required'); return; } + + if (params.apiConfig) { + await configurator.init(params.apiConfig); + if (params.apiConfig.name) { + const userInfo = await auth.login(params.apiConfig.name); + if (!userInfo) { + console.error('failed to fetch user'); + return; + } + } + } + + mainApp = new MainApp(); + mainApp.loadAConfig({ + container: params.container || 'renderDom', + modelUrlList: params.modelUrlList || [], + success: params.onSuccess ?? null, + error: params.onError ?? null + }); + + await mainApp.Awake(); + await mainApp.loadModel(); + + this.debugMorphTargets(); + } +}; + +if (!window.faceSDK) { + window.faceSDK = {}; +} +window.faceSDK.kernel = kernel; + +if (!window.yiyu) { + window.yiyu = {}; +} +window.yiyu.kernel = kernel; + +window.yiyu.onAppLoaded = () => { }; +window.yiyu.onSingleSignFinished = (_text: string) => { }; +window.yiyu.onSentenceFinished = (_text: string) => { }; +window.yiyu.onSentenceChanged = (_data: unknown) => { }; +window.yiyu.onGloss = (_gloss: unknown) => { }; + +window.onload = () => { }; + +export { kernel }; diff --git a/src/managers/BaseManager.ts b/src/managers/BaseManager.ts new file mode 100644 index 0000000..4f1ec98 --- /dev/null +++ b/src/managers/BaseManager.ts @@ -0,0 +1,19 @@ +import { Monobehiver } from '../base/Monobehiver'; +import { Dictionary } from '../utils/Dictionary'; + +/** + * 管理器基类 - 提供通用的初始化和缓存能力 + */ +export class BaseManager extends Monobehiver { + protected meshCache = new Dictionary(); + protected isInitialized = false; + + async initialize(): Promise { + if (this.isInitialized) return; + this.isInitialized = true; + } + + clean(): void { + this.meshCache.Clear(); + } +} diff --git a/src/types/js-md5.d.ts b/src/types/js-md5.d.ts new file mode 100644 index 0000000..dd54021 --- /dev/null +++ b/src/types/js-md5.d.ts @@ -0,0 +1,3 @@ +declare module 'js-md5' { + export default function md5(message: string | ArrayBuffer | ArrayBufferView): string; +} diff --git a/src/utils/Dictionary.ts b/src/utils/Dictionary.ts new file mode 100644 index 0000000..8f6fcb9 --- /dev/null +++ b/src/utils/Dictionary.ts @@ -0,0 +1,50 @@ +/** + * @file Dictionary.js + * @description 字典工具类 - 键值对存储 + */ + +/** + * 字典类 - 提供类似Map的键值对存储功能 + */ +export class Dictionary { + private items: Record = {}; + + /** 检查是否存在指定键 */ + Has(key: string): boolean { + return key in this.items; + } + + /** 设置键值对 */ + Set(key: string, value: T): void { + this.items[key] = value; + } + + /** 移除指定键 */ + Remove(key: string): boolean { + if (this.Has(key)) { + delete this.items[key]; + return true; + } + return false; + } + + /** 获取指定键的值 */ + Get(key: string): T | undefined { + return this.Has(key) ? this.items[key] : undefined; + } + + /** 获取所有键 */ + Keys(): string[] { + return Object.keys(this.items); + } + + /** 获取所有值 */ + Values(): T[] { + return Object.values(this.items); + } + + /** 清空字典 */ + Clear(): void { + this.items = {}; + } +} diff --git a/src/utils/compressor.ts b/src/utils/compressor.ts new file mode 100644 index 0000000..cb513a4 --- /dev/null +++ b/src/utils/compressor.ts @@ -0,0 +1,25 @@ +/** + * @file compressor.js + * @description 解压缩工具,使用pako完成与GoBetterStudio相同的deflate/raw解码 + */ + +import pako from 'pako'; + +const decoder = new TextDecoder(); + +export const compressor = { + /** 压缩字符串或字节数据 */ + compress(data: Uint8Array | string): Uint8Array { + const source = typeof data === 'string' ? new TextEncoder().encode(data) : data; + return pako.deflateRaw(source); + }, + + /** 解压缩字节数据并返回字符串 */ + decompress(data: ArrayBuffer | Uint8Array): string { + const input = data instanceof Uint8Array ? data : new Uint8Array(data); + const uncompressed = pako.inflateRaw(input); + return decoder.decode(uncompressed); + } +}; + +export default compressor; diff --git a/src/utils/cryptor.ts b/src/utils/cryptor.ts new file mode 100644 index 0000000..d7e7834 --- /dev/null +++ b/src/utils/cryptor.ts @@ -0,0 +1,73 @@ +import md5 from 'js-md5'; + +const KEY_SIZE = 256 / 32; +const ITERATIONS = 1000; + +type EncryptedAsset = { + value: Uint8Array; + Timestamp: string; +}; + +type Credential = { + token: string; + uid: string; +}; + +type CryptoMaterial = { + obj: Uint8Array; + iv: Uint8Array; + salt: Uint8Array; + key: string; +}; + +function prepareCryptoMaterial(data: EncryptedAsset, info: Credential): CryptoMaterial { + const userIdPrefix = info.uid.slice(0, 16); + const iv = new TextEncoder().encode(userIdPrefix); + const obj = data.value.slice(7); + const saltHex = md5(`${info.token}${data.Timestamp}`); + const saltBytes = new Uint8Array((saltHex.match(/.{1,2}/g) ?? []).map(byte => parseInt(byte, 16))); + return { obj, iv, salt: saltBytes, key: info.token }; +} + +/** 从服务器返回的数据中异步解密出原始GLTF内容 */ +export async function decryptAsync(data: EncryptedAsset, info: Credential): Promise { + const { obj, iv, salt, key } = prepareCryptoMaterial(data, info); + const derivedKey = await generateAesKeyAsync(key, salt, KEY_SIZE, ITERATIONS); + return aesDecryptAsync(obj, derivedKey, iv); +} + +async function generateAesKeyAsync(secret: string, salt: Uint8Array, keySize: number, iterations: number): Promise { + const passwordBuffer = new TextEncoder().encode(secret); + const baseKey = await crypto.subtle.importKey( + 'raw', + passwordBuffer, + { name: 'PBKDF2' }, + false, + ['deriveBits', 'deriveKey'] + ); + return crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt, + iterations, + hash: 'SHA-1' + }, + baseKey, + { name: 'AES-CBC', length: keySize * 32 }, + false, + ['decrypt'] + ); +} + +async function aesDecryptAsync(data: Uint8Array, key: CryptoKey, iv: Uint8Array): Promise { + try { + return await crypto.subtle.decrypt( + { name: 'AES-CBC', iv }, + key, + data + ); + } catch (error) { + console.error('解密失败:', error); + return null; + } +} diff --git a/src/utils/event.ts b/src/utils/event.ts new file mode 100644 index 0000000..7132077 --- /dev/null +++ b/src/utils/event.ts @@ -0,0 +1,123 @@ +type Listener = { + callback: (...args: unknown[]) => void; + context?: unknown; +}; + +type EventMeta = { + type: string; + description: string; + listeners: Listener[]; +}; + +/** + * 基础事件发射器 + */ +export class Emitter { + private _events: Record = {}; + + on(name: string, callback: (...args: unknown[]) => void, context?: unknown): this { + if (!this._events[name]) { + this._events[name] = []; + } + this._events[name].push({ callback, context }); + return this; + } + + once(name: string, callback: (...args: unknown[]) => void, context?: unknown): this { + const onceWrapper = (...args: unknown[]) => { + this.off(name, onceWrapper); + callback.apply(context, args); + }; + return this.on(name, onceWrapper, context); + } + + off(name?: string, callback?: (...args: unknown[]) => void): this { + if (!name) { + this._events = {}; + return this; + } + if (!this._events[name]) return this; + if (!callback) { + delete this._events[name]; + return this; + } + this._events[name] = this._events[name].filter( + listener => listener.callback !== callback + ); + return this; + } + + removeAllListeners(): this { + this._events = {}; + return this; + } + + emit(name: string, ...args: unknown[]): this { + if (!this._events[name]) return this; + this._events[name].forEach(listener => { + listener.callback.apply(listener.context, args); + }); + return this; + } + + listenerCount(name: string): number { + return this._events[name]?.length ?? 0; + } +} + +/** + * 全局事件管理器 + */ +export class EventManager extends Emitter { + private eventMap: Map; + + constructor() { + super(); + this.eventMap = new Map(); + } + + registerEvent(type: string, description: string): void { + this.eventMap.set(type, { type, description, listeners: [] }); + } + + getRegisteredEvents(): EventMeta[] { + return Array.from(this.eventMap.values()); + } +} + +// 创建全局事件管理器实例 +export const eventManager = new EventManager(); + +// 注册标准事件类型(描述使用英文,避免编码问题) +eventManager.registerEvent('load', 'resource load complete'); +eventManager.registerEvent('load-progress', 'resource load progress'); +eventManager.registerEvent('load-error', 'resource load error'); +eventManager.registerEvent('animation-start', 'animation start'); +eventManager.registerEvent('animation-end', 'animation end'); +eventManager.registerEvent('animation-loop', 'animation loop'); +eventManager.registerEvent('model-change', 'model change'); +eventManager.registerEvent('camera-change', 'camera change'); +eventManager.registerEvent('scene-ready', 'scene ready'); +eventManager.registerEvent('dispose', 'component disposed'); + +// 导出便捷函数 +export function on(eventName: string, callback: (...args: unknown[]) => void, context?: unknown): Emitter { + return eventManager.on(eventName, callback, context); +} + +export function off(eventName?: string, callback?: (...args: unknown[]) => void): Emitter { + return eventManager.off(eventName, callback); +} + +export function once(eventName: string, callback: (...args: unknown[]) => void, context?: unknown): Emitter { + return eventManager.once(eventName, callback, context); +} + +export function emit(eventName: string, ...args: unknown[]): Emitter { + return eventManager.emit(eventName, ...args); +} + +export function removeAllListeners(eventName?: string): Emitter { + if (eventName) return eventManager.off(eventName); + return eventManager.removeAllListeners(); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..de70fa5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "allowJs": true, + "resolveJsonModule": true, + "isolatedModules": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "types": ["vite/client"] + }, + "include": ["src/**/*", "index.html"] +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..e4432c2 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,37 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + resolve: { + alias: { + '@': '/src' + } + }, + build: { + lib: { + entry: 'src/main.ts', + name: 'kernel', + formats: ['esm'], + fileName: () => 'assets/index.js', + }, + target: 'esnext', + outDir: 'dist', + assetsDir: 'assets', + copyPublicDir: true, + minify: false, + reportCompressedSize: false, + rollupOptions: { + output: { + inlineDynamicImports: true, + }, + }, + }, + optimizeDeps: { + exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'] + }, + server: { + headers: { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp' + } + } +})