From 0855f15a5c041b996217fb4a3f65a91fa0715a20 Mon Sep 17 00:00:00 2001 From: "2817212736@qq.com" <2817212736@qq.com> Date: Thu, 10 Apr 2025 16:37:20 +0800 Subject: [PATCH] Init --- .gitignore | 24 + .vscode/extensions.json | 3 + README.md | 5 + index.html | 13 + package.json | 29 + pnpm-lock.yaml | 960 +++++++++++++++++++++++++++ src/App.vue | 11 + src/assets/vue.svg | 1 + src/common/CameraControls.ts | 251 ++++++++ src/common/GeUtils.ts | 290 +++++++++ src/common/KeyEnum.ts | 164 +++++ src/common/MaterialEditor.ts | 236 +++++++ src/common/MaterialMouseControl.ts | 85 +++ src/common/Matrix2.ts | 33 + src/common/PMREMGenerator2.ts | 997 +++++++++++++++++++++++++++++ src/common/PlaneExt.ts | 46 ++ src/common/Singleton.ts | 40 ++ src/common/Viewer.ts | 168 +++++ src/components/MaterialView.vue | 41 ++ src/main.ts | 6 + src/vite-env.d.ts | 1 + tsconfig.app.json | 14 + tsconfig.json | 7 + tsconfig.node.json | 24 + vite.config.ts | 7 + 25 files changed, 3456 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/App.vue create mode 100644 src/assets/vue.svg create mode 100644 src/common/CameraControls.ts create mode 100644 src/common/GeUtils.ts create mode 100644 src/common/KeyEnum.ts create mode 100644 src/common/MaterialEditor.ts create mode 100644 src/common/MaterialMouseControl.ts create mode 100644 src/common/Matrix2.ts create mode 100644 src/common/PMREMGenerator2.ts create mode 100644 src/common/PlaneExt.ts create mode 100644 src/common/Singleton.ts create mode 100644 src/common/Viewer.ts create mode 100644 src/components/MaterialView.vue create mode 100644 src/main.ts create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c62aeb --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "material-editor", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.13", + "three": "npm:three-cf@^0.122.9", + "js-angusj-clipper": "^1.2.1", + "polylabel": "^1.1.0", + "@jscad/modeling": "^2.11.0", + "flatbush": "^3.3.0", + "xaop": "^2.0.0", + "webcad_ue4_api": "http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "@vue/tsconfig": "^0.7.0", + "typescript": "~5.7.2", + "vite": "^6.2.0", + "vue-tsc": "^2.2.4" + }, + "packageManager": "pnpm@9.1.1+sha1.09ada6cd05003e0ced25fb716f9fda4063ec2e3b" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c60c76b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,960 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@jscad/modeling': + specifier: ^2.11.0 + version: 2.12.5 + flatbush: + specifier: ^3.3.0 + version: 3.3.1 + js-angusj-clipper: + specifier: ^1.2.1 + version: 1.3.1 + polylabel: + specifier: ^1.1.0 + version: 1.1.0 + three: + specifier: npm:three-cf@^0.122.9 + version: three-cf@0.122.9 + vue: + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.3) + webcad_ue4_api: + specifier: http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz + version: http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz + xaop: + specifier: ^2.0.0 + version: 2.1.0 + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.2.1 + version: 5.2.3(vite@6.2.5)(vue@3.5.13(typescript@5.7.3)) + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) + typescript: + specifier: ~5.7.2 + version: 5.7.3 + vite: + specifier: ^6.2.0 + version: 6.2.5 + vue-tsc: + specifier: ^2.2.4 + version: 2.2.8(typescript@5.7.3) + +packages: + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jscad/modeling@2.12.5': + resolution: {integrity: sha512-7deHWmE+CKBx2RyxDxl9Pie0bDOHHtcsh4V0gKViu27abYXKGl8+myoddwIvxLczwnw31ZZXDdUdm0Ef51YwQg==} + + '@rollup/rollup-android-arm-eabi@4.39.0': + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.39.0': + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.39.0': + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.39.0': + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.39.0': + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.39.0': + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.39.0': + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.39.0': + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.39.0': + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.39.0': + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@vitejs/plugin-vue@5.2.3': + resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.12': + resolution: {integrity: sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==} + + '@volar/source-map@2.4.12': + resolution: {integrity: sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==} + + '@volar/typescript@2.4.12': + resolution: {integrity: sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==} + + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.8': + resolution: {integrity: sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + + '@vue/tsconfig@0.7.0': + resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + alien-signals@1.0.13: + resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + flatbush@3.3.1: + resolution: {integrity: sha512-oKuPbtT+DS2CxH+9Vhbsq8HifmSCuOw+3Cy5zt/vCIrZl5KyengoTHDBLmtpZoBhcwa7/biNjgL1DwdLMJYm1A==} + + flatqueue@1.2.1: + resolution: {integrity: sha512-X86TpWS1rGuY7m382HuA9vngLeDuWA9lJvhEG+GfgKMV5onSvx5a71cl7GMbXzhWtlN9dGfqOBrpfqeOtUfGYQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + js-angusj-clipper@1.3.1: + resolution: {integrity: sha512-/qru4QXxN/gBbQjL4WaFl296YSM8kh5XKpNuNqfZhJ4t4Hw3KeLc5ERj3XHAeLi6pBrqeh6o9PFZUpS3QThEEQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + polylabel@1.1.0: + resolution: {integrity: sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + three-cf@0.122.9: + resolution: {integrity: sha512-y+bvPYKI0yMNGF2flNsTbqloPiMAL4OKbIbR9QaQLRjNlz15Th+69ZtirU5ZASGXF/vQIvIrv+6OkxdSjiN89Q==} + + tinyqueue@2.0.3: + resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + vite@6.2.5: + resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-tsc@2.2.8: + resolution: {integrity: sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webcad_ue4_api@http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz: + resolution: {tarball: http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz} + version: 0.3.19 + + xaop@2.1.0: + resolution: {integrity: sha512-/ovWCaQIet3a3VnoVeN1/pNPqCrS/JifF28N7crPhq8BXEWx4Da2o+LYCCxnTWGAjGp5+2W2hd0HIpVESounSQ==} + +snapshots: + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@esbuild/aix-ppc64@0.25.2': + optional: true + + '@esbuild/android-arm64@0.25.2': + optional: true + + '@esbuild/android-arm@0.25.2': + optional: true + + '@esbuild/android-x64@0.25.2': + optional: true + + '@esbuild/darwin-arm64@0.25.2': + optional: true + + '@esbuild/darwin-x64@0.25.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.2': + optional: true + + '@esbuild/freebsd-x64@0.25.2': + optional: true + + '@esbuild/linux-arm64@0.25.2': + optional: true + + '@esbuild/linux-arm@0.25.2': + optional: true + + '@esbuild/linux-ia32@0.25.2': + optional: true + + '@esbuild/linux-loong64@0.25.2': + optional: true + + '@esbuild/linux-mips64el@0.25.2': + optional: true + + '@esbuild/linux-ppc64@0.25.2': + optional: true + + '@esbuild/linux-riscv64@0.25.2': + optional: true + + '@esbuild/linux-s390x@0.25.2': + optional: true + + '@esbuild/linux-x64@0.25.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.2': + optional: true + + '@esbuild/netbsd-x64@0.25.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.2': + optional: true + + '@esbuild/openbsd-x64@0.25.2': + optional: true + + '@esbuild/sunos-x64@0.25.2': + optional: true + + '@esbuild/win32-arm64@0.25.2': + optional: true + + '@esbuild/win32-ia32@0.25.2': + optional: true + + '@esbuild/win32-x64@0.25.2': + optional: true + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jscad/modeling@2.12.5': {} + + '@rollup/rollup-android-arm-eabi@4.39.0': + optional: true + + '@rollup/rollup-android-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-x64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.39.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.39.0': + optional: true + + '@types/estree@1.0.7': {} + + '@vitejs/plugin-vue@5.2.3(vite@6.2.5)(vue@3.5.13(typescript@5.7.3))': + dependencies: + vite: 6.2.5 + vue: 3.5.13(typescript@5.7.3) + + '@volar/language-core@2.4.12': + dependencies: + '@volar/source-map': 2.4.12 + + '@volar/source-map@2.4.12': {} + + '@volar/typescript@2.4.12': + dependencies: + '@volar/language-core': 2.4.12 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.27.0 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.27.0 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.3 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.8(typescript@5.7.3)': + dependencies: + '@volar/language-core': 2.4.12 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.13 + alien-signals: 1.0.13 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.7.3 + + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.3))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.7.3) + + '@vue/shared@3.5.13': {} + + '@vue/tsconfig@0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))': + optionalDependencies: + typescript: 5.7.3 + vue: 3.5.13(typescript@5.7.3) + + alien-signals@1.0.13: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + csstype@3.1.3: {} + + de-indent@1.0.2: {} + + entities@4.5.0: {} + + esbuild@0.25.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 + + estree-walker@2.0.2: {} + + flatbush@3.3.1: + dependencies: + flatqueue: 1.2.1 + + flatqueue@1.2.1: {} + + fsevents@2.3.3: + optional: true + + he@1.2.0: {} + + js-angusj-clipper@1.3.1: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + polylabel@1.1.0: + dependencies: + tinyqueue: 2.0.3 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.39.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + three-cf@0.122.9: {} + + tinyqueue@2.0.3: {} + + typescript@5.7.3: {} + + vite@6.2.5: + dependencies: + esbuild: 0.25.2 + postcss: 8.5.3 + rollup: 4.39.0 + optionalDependencies: + fsevents: 2.3.3 + + vscode-uri@3.1.0: {} + + vue-tsc@2.2.8(typescript@5.7.3): + dependencies: + '@volar/typescript': 2.4.12 + '@vue/language-core': 2.2.8(typescript@5.7.3) + typescript: 5.7.3 + + vue@3.5.13(typescript@5.7.3): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.3)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.7.3 + + webcad_ue4_api@http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz: {} + + xaop@2.1.0: {} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..4b70bc0 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/common/CameraControls.ts b/src/common/CameraControls.ts new file mode 100644 index 0000000..88588ff --- /dev/null +++ b/src/common/CameraControls.ts @@ -0,0 +1,251 @@ +import * as THREE from 'three'; + +import { Vector3 } from 'three'; +import { Viewer } from './Viewer'; +import { KeyBoard, MouseKey } from './KeyEnum'; + + +//相机控制状态 +export enum CameraControlState +{ + Null = 0, Pan = 1, Rotate = 2, Scale = 3 +} + +export class CameraControls +{ + m_TouthTypeList = [CameraControlState.Rotate, CameraControlState.Scale, CameraControlState.Pan]; + m_domElement: HTMLElement;//HTMLDocument + //起始点击 + m_StartClickPoint: THREE.Vector3 = new THREE.Vector3(); + m_EndClickPoint: THREE.Vector3 = new THREE.Vector3(); + m_DollyStart: THREE.Vector2 = new THREE.Vector2(); + m_DollyEnd: THREE.Vector2 = new THREE.Vector2(); + + m_KeyDown = new Map(); + m_MouseDown = new Map(); + + //状态 + m_State: CameraControlState = CameraControlState.Null; + m_Viewer: Viewer; + //左键使用旋转 + m_LeftUseRotate: boolean = true; + + constructor(viewer: Viewer) + { + this.m_Viewer = viewer; + this.m_domElement = viewer.Renderer.domElement.parentElement; + this.RegisterEvent(); + } + + RegisterEvent() + { + if (this.m_domElement) + { + this.m_domElement.addEventListener("mousedown", this.onMouseDown, false) + this.m_domElement.addEventListener("mousemove", this.onMouseMove, false) + this.m_domElement.addEventListener("mouseup", this.onMouseUp, false) + window.addEventListener("keydown", this.onKeyDown, false); + window.addEventListener("keyup", this.onKeyUp, false); + this.m_domElement.addEventListener('wheel', this.onMouseWheel, false); + + this.m_domElement.addEventListener('touchstart', this.onTouchStart, false); + this.m_domElement.addEventListener('touchend', this.onTouchEnd, false); + this.m_domElement.addEventListener('touchmove', this.onTouchMove, false); + + window.addEventListener("blur", this.onBlur, false); + } + } + /** + * 窗体失去焦点时. + * @memberof CameraControls + */ + onBlur = () => + { + this.m_KeyDown.clear(); + this.m_MouseDown.clear(); + } + + //触屏开始事件 + onTouchStart = (event: TouchEvent) => + { + this.m_Viewer.UpdateLockTarget(); + this.m_StartClickPoint.set(event.touches[0].pageX, event.touches[0].pageY, 0); + if (event.touches.length < 4) + { + if (event.touches.length == 2) + { + var dx = event.touches[0].pageX - event.touches[1].pageX; + var dy = event.touches[0].pageY - event.touches[1].pageY; + var distance = Math.sqrt(dx * dx + dy * dy); + this.m_DollyStart.set(0, distance); + } + this.m_State = this.m_TouthTypeList[event.touches.length - 1]; + } + } + onTouchEnd = (event: TouchEvent) => + { + this.m_State = CameraControlState.Null; + } + onTouchMove = (event: TouchEvent) => + { + event.preventDefault(); + event.stopPropagation(); + + this.m_EndClickPoint.set(event.touches[0].pageX, event.touches[0].pageY, 0); + + let vec = this.m_EndClickPoint.clone().sub(this.m_StartClickPoint); + switch (this.m_State) + { + case CameraControlState.Pan: + { + this.m_Viewer.Pan(vec); + break; + } + case CameraControlState.Scale: + { + var dx = event.touches[0].pageX - event.touches[1].pageX; + var dy = event.touches[0].pageY - event.touches[1].pageY; + + var distance = Math.sqrt(dx * dx + dy * dy); + this.m_DollyEnd.set(0, distance); + if (distance > this.m_DollyStart.y) + { + this.m_Viewer.Zoom(0.95); + } + else + { + this.m_Viewer.Zoom(1.05) + } + this.m_DollyStart.copy(this.m_DollyEnd); + break; + } + case CameraControlState.Rotate: + { + this.m_Viewer.Rotate(vec.multiplyScalar(2)); + break; + } + } + this.m_StartClickPoint.copy(this.m_EndClickPoint); + this.m_Viewer.UpdateRender(); + } + beginRotate() + { + this.m_State = CameraControlState.Rotate; + this.m_Viewer.UpdateLockTarget(); + } + + //最后一次按中键的时间 + lastMiddleClickTime = 0; + //鼠标 + onMouseDown = (event: MouseEvent) => + { + event.preventDefault(); + let key: MouseKey = event.button; + this.m_MouseDown.set(key, true); + this.m_StartClickPoint.set(event.offsetX, event.offsetY, 0); + + switch (key) + { + case MouseKey.Left: + { + if (this.m_LeftUseRotate) + { + this.beginRotate(); + } + break; + } + case MouseKey.Middle: + { + let curTime = Date.now(); + let t = curTime - this.lastMiddleClickTime; + this.lastMiddleClickTime = curTime; + if (t < 350) + { + this.m_Viewer.ZoomAll(); + return; + } + if (this.m_KeyDown.get(KeyBoard.Control)) + { + this.beginRotate(); + } + else + { + this.m_State = CameraControlState.Pan; + } + break; + } + case MouseKey.Right: + { + break; + } + } + } + onMouseUp = (event: MouseEvent) => + { + event.preventDefault(); + this.m_State = CameraControlState.Null; + this.m_MouseDown.set(event.button, false); + } + onMouseMove = (event: MouseEvent) => + { + event.preventDefault(); + this.m_EndClickPoint.set(event.offsetX, event.offsetY, 0); + let changeVec = this.m_EndClickPoint.clone().sub(this.m_StartClickPoint); + this.m_StartClickPoint.copy(this.m_EndClickPoint); + if ( + (this.m_LeftUseRotate || + (this.m_KeyDown.get(KeyBoard.Control)) + ) + && this.m_State == CameraControlState.Rotate + ) + { + this.m_Viewer.Rotate(changeVec); + } + switch (this.m_State) + { + case CameraControlState.Pan: + { + this.m_Viewer.Pan(changeVec); + break; + } + case CameraControlState.Rotate: + { + break; + } + case CameraControlState.Scale: + { + break; + } + } + } + /** + * 鼠标滚轮事件 + * + * @memberof CameraControls + */ + onMouseWheel = (event: WheelEvent) => + { + event.stopPropagation(); + + let pt = new THREE.Vector3(event.offsetX, event.offsetY, 0); + + this.m_Viewer.ScreenToWorld(pt, new Vector3().setFromMatrixColumn(this.m_Viewer.m_Camera.Camera.matrixWorld, 2)); + if (event.deltaY < 0) + { + this.m_Viewer.Zoom(0.6, pt); + } + else if (event.deltaY > 0) + { + this.m_Viewer.Zoom(1.4, pt); + } + } + //按键 + onKeyDown = (event: KeyboardEvent) => + { + this.m_KeyDown.set(event.keyCode, true); + } + onKeyUp = (event: KeyboardEvent) => + { + this.m_KeyDown.set(event.keyCode, false); + } +} diff --git a/src/common/GeUtils.ts b/src/common/GeUtils.ts new file mode 100644 index 0000000..73a64a0 --- /dev/null +++ b/src/common/GeUtils.ts @@ -0,0 +1,290 @@ +import { Box3, Geometry, Line, Matrix4, Mesh, Object3D, Vector2, Vector3, type Vector } from 'three'; +import { Matrix2 } from './Matrix2'; + + +export const cZeroVec = new Vector3(); +export const cXAxis = new Vector3(1, 0, 0); +export const cYAxis = new Vector3(0, 1, 0); +export const cZAxis = new Vector3(0, 0, 1); + +/** + * 旋转一个点,旋转中心在原点 + * + * @export + * @param {Vector3} pt 点 + * @param {number} ang 角度. + * @returns {Vector3} 返回pt不拷贝. + */ +export function rotatePoint(pt: Vector3, ang: number): Vector3 +{ + new Matrix2().setRotate(ang).applyVector(pt); + return pt; +} + +export function equaln(v1: number, v2: number, fuzz = 1e-3) +{ + return Math.abs(v1 - v2) < fuzz; +} + +export function equalv3(v1: Vector3, v2: Vector3, fuzz = 1e-8) +{ + return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz) && equaln(v1.z, v2.z, fuzz); +} + +export function equal(v1: T, v2: T) +{ + return v1.distanceToSquared(v2) < 1e-8; +} + +export function fixAngle(an: number, fixAngle: number, fuzz: number = 0.1) +{ + if (an < 0) + an += Math.PI * 2; + an += fuzz; + let rem = an % fixAngle; + if (rem < fuzz * 2) + { + an -= rem; + } + else + { + an -= fuzz; + } + return an; +} + +/** + * 按照极坐标的方式移动一个点 + * + * @export + * @template + * @param {T} v 向量(2d,3d) + * @param {number} an 角度 + * @param {number} dis 距离 + * @returns {T} + */ +export function polar(v: T, an: number, dis: number): T +{ + v.x += Math.cos(an) * dis; + v.y += Math.sin(an) * dis; + return v; +} + +export function angle(v: Vector3 | Vector2) +{ + if (equaln(v.y, 0) && v.x > 0) + return 0; + let angle = Math.atan2(v.y, v.x); + if (angle < 0) angle += Math.PI * 2; + return angle; +} + +/** + * 求两个向量的夹角,顺时针为负,逆时针为正 + * + * @param {Vector3} v1 + * @param {Vector3} v2 + * @param {Vector3} [ref] 参考向量,如果为世界坐标系则为0,0,1 + * @returns + */ +export function angleTo(v1: Vector3, v2: Vector3, ref: Vector3 = new Vector3(0, 0, 1)) +{ + if (!ref.equals(new Vector3(0, 0, 1))) + { + //任意轴坐标系. 使用相机的构造矩阵. + ref.multiplyScalar(-1); + let up = getLoocAtUpVec(ref); + let refOcs = new Matrix4(); + refOcs.lookAt(cZeroVec, ref, up); + let refOcsInv = new Matrix4().getInverse(refOcs); + v1.applyMatrix4(refOcsInv); + v2.applyMatrix4(refOcsInv); + v1.z = 0; + v2.z = 0; + } + if (v1.equals(cZeroVec) || v2.equals(cZeroVec)) + return 0; + let cv = new Vector3().crossVectors(v1, v2).normalize(); + return cv.z === 0 ? v1.angleTo(v2) : v1.angleTo(v2) * cv.z; +} + +export function getLoocAtUpVec(dir: Vector3): Vector3 +{ + if (dir.equals(cZeroVec)) + { + throw ("zero vector"); + } + let norm = dir.clone().normalize(); + if (norm.equals(cZAxis)) + { + return new Vector3(0, 1, 0); + } + else if (norm.equals(cZAxis.clone().negate())) + { + return new Vector3(0, -1, 0); + } + else + { + let xv: Vector3 = new Vector3(); + xv.crossVectors(cZAxis, norm); + + let up = new Vector3(); + up.crossVectors(norm, xv); + return up; + } +} + +export function createLookAtMat4(dir: Vector3): Matrix4 +{ + let up = getLoocAtUpVec(dir); + let mat = new Matrix4(); + mat.lookAt(cZeroVec, dir, up); + return mat; +} + +export function isParallelTo(v1: Vector3, v2: Vector3) +{ + return v1.clone().cross(v2).lengthSq() < 1e-9; +} + +export function ptToString(v: Vector3, fractionDigits: number = 3): string +{ + return v.toArray().map(o => + { + return o.toFixed(fractionDigits); + }).join(","); +} + +export function midPoint(v1: Vector3, v2: Vector3): Vector3 +{ + return v1.clone().add(v2).multiplyScalar(0.5); +} +export function midPoint2(v1: Vector2, v2: Vector2): Vector2 +{ + return v1.clone().add(v2).multiplyScalar(0.5); +} + +export function midPtCir(v1: Vector3, v2: Vector3) +{ + let baseline = new Vector3(1, 0, 0); + let outLine = v2.clone().sub(v1); + let ang = angleTo(baseline, outLine) / 2; + let midLine = rotatePoint(outLine, -ang); + return v1.clone().add(midLine); +} + +/** + * 获得Three对象的包围盒. + * @param obj + * @param [updateMatrix] 是否应该更新对象矩阵 + * @returns box + */ +export function GetBox(obj: Object3D, updateMatrix?: boolean): Box3 +{ + let box = new Box3(); + if (updateMatrix) obj.updateMatrixWorld(false); + if (!obj.visible) return box; + + obj.traverse(o => + { + //因为实体Erase时,实体仍然保存在Scene中. + if (o.visible === false) + return; + + //@ts-ignore + let geo = o.geometry as BufferGeometry; + if (geo) + { + if (!geo.boundingBox) + geo.computeBoundingBox(); + box.union(geo.boundingBox.clone().applyMatrix4(o.matrixWorld)); + } + }); + + return box; +} + +export function GetBoxArr(arr: Array): Box3 +{ + let box = new Box3(); + for (let o of arr) + { + let b = GetBox(o); + if (!b.isEmpty()) + box.union(b); + } + return box; +} + +export function MoveMatrix(v: Vector3): Matrix4 +{ + let mat = new Matrix4(); + mat.makeTranslation(v.x, v.y, v.z); + return mat; +} + +export function getProjectDist(v1: Vector3, v2: Vector3) +{ + let ang = v1.angleTo(v2); + let dist = v1.length(); + return { + h: dist * Math.cos(ang), + v: dist * Math.sin(ang) + }; +} +//获得输入点在2线组成的4个区间的位置 +export function getPtPostion(sp: Vector3, ep: Vector3, c: Vector3, inPt: Vector3) +{ + let l1 = sp.clone().sub(c); + let l2 = ep.clone().sub(c); + let l3 = l1.clone().negate(); + let l4 = l2.clone().negate(); + let inputLine = inPt.clone().sub(c); + let ang1 = angleTo(l1, l2); + let ang2 = Math.PI; + let ang3 = ang2 + Math.abs(ang1); + let inputAng = angleTo(l1, inputLine); + if (ang1 * inputAng < 0) + { + inputAng = (Math.PI * 2 - Math.abs(inputAng)); + } + ang1 = Math.abs(ang1); + inputAng = Math.abs(inputAng); + if (inputAng <= ang1) + { + return { sp, ep }; + } else if (inputAng > ang1 && inputAng <= ang2) + { + return { sp: c.clone().add(l3), ep }; + } else if (inputAng > ang2 && inputAng <= ang3) + { + return { sp: c.clone().add(l3), ep: c.clone().add(l4) }; + } else + { + return { sp, ep: c.clone().add(l4) }; + } +} +export function angleAndX(v: Vector3 | Vector2) +{ + return v.x ? Math.atan(v.y / v.x) : Math.PI / 2; +} +/** + * 将角度调整为0-2pi之间 + * + * @export + * @param {number} an + */ +export function angleTo2Pi(an: number) +{ + an = an % (Math.PI * 2); + if (an < 0) an += Math.PI * 2; + return an; +} +export function updateGeometry(l: Line | Mesh, geometry: Geometry) +{ + let geo = l.geometry as Geometry; + geo.dispose(); + l.geometry = geometry; + geometry.verticesNeedUpdate = true; + geometry.computeBoundingSphere(); +} diff --git a/src/common/KeyEnum.ts b/src/common/KeyEnum.ts new file mode 100644 index 0000000..f9f2767 --- /dev/null +++ b/src/common/KeyEnum.ts @@ -0,0 +1,164 @@ +//鼠标类型 +export enum MouseKey +{ + Left = 0, + Middle = 1, + Right = 2, +} + +export enum KeyBoard +{ + // 数字 + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + Digit0 = 58, + // 字母 + KeyA = 65, + KeyB = 66, + KeyC = 67, + KeyD = 68, + KeyE = 69, + KeyF = 70, + KeyG = 71, + KeyH = 72, + KeyI = 73, + KeyJ = 74, + KeyK = 75, + KeyL = 76, + KeyM = 77, + KeyN = 78, + KeyO = 79, + KeyP = 80, + KeyQ = 81, + KeyR = 82, + KeyS = 83, + KeyT = 84, + KeyU = 85, + KeyV = 86, + KeyW = 87, + KeyX = 88, + KeyY = 89, + KeyZ = 90, + + // 符号 + /** + * 逗号 + */ + Comma = 188, + CommaChrome = 229, + /** + * 句号 + */ + Period = 190, + /** + * 分号 + */ + Semicolon = 186, + /** + * 引号 + */ + Quote = 222, + /** + * 左括号 + */ + BracketLeft = 219, + /** + * 右括号 + */ + BracketRight = 220, + /** + * 反引号 + */ + Backquote = 192, + /** + * 反斜杠 + */ + Backslash = 220, + /** + * 减号 + */ + Minus = 189, + /** + * 等号 + */ + Equal = 187, + IntlRo = 193, + IntlYen = 255, + // 功能键 + Alt = 18, + /** + * 大写锁定 + */ + CapsLock = 20, + Control = 17, + /** + * win左键 + */ + OSLeft = 91, + /** + * win右键 + */ + OSRight = 92, + Shift = 16, + + ContextMenu = 93, + Enter = 13, + Space = 32, + Backspace = 8, + Tab = 9, + Delete = 46, + End = 35, + Home = 36, + Insert = 45, + PageDown = 34, + PageUp = 33, + ArrowDown = 40, + ArrowLeft = 37, + ArrowRight = 39, + ArrowUp = 38, + Escape = 27, + PrintScreen = 44, + ScrollLock = 145, + Pause = 19, + + // F数字 + F1 = 112, + F2 = 113, + F3 = 114, + F5 = 116, + F6 = 117, + F7 = 118, + F8 = 119, + F9 = 120, + F10 = 121, + F11 = 122, + F12 = 123, + + //数字键盘 + NumLock = 114, + Numpad0 = 96, + Numpad1 = 97, + Numpad2 = 98, + Numpad3 = 99, + Numpad4 = 100, + Numpad5 = 101, + Numpad6 = 102, + Numpad7 = 103, + Numpad8 = 104, + Numpad9 = 105, + NumpadAdd = 107, + NumpadDivide = 111, + NumpadEqual = 12, + NumpadMultiply = 106, + NumpadSubtract = 109, + NumpadDot = 110, + NumpadDot1 = 190 + +} diff --git a/src/common/MaterialEditor.ts b/src/common/MaterialEditor.ts new file mode 100644 index 0000000..c511a95 --- /dev/null +++ b/src/common/MaterialEditor.ts @@ -0,0 +1,236 @@ + +import { AmbientLight, BoxBufferGeometry, BufferGeometry, ConeBufferGeometry, CubeRefractionMapping, CubeTextureLoader, Geometry, Mesh, MeshPhysicalMaterial, Object3D, SphereBufferGeometry, sRGBEncoding, Texture, TorusBufferGeometry, TorusKnotBufferGeometry } from 'three'; +import { Singleton } from './Singleton'; +import { Viewer } from './Viewer'; +import { PMREMGenerator3 } from './PMREMGenerator2'; +import type { PhysicalMaterialRecord, TextureTableRecord } from 'webcad_ue4_api'; +import { MaterialEditorCamerControl } from './MaterialMouseControl'; +import { ref } from 'vue'; + +async function textureRenderUpdate(textureRecord:TextureTableRecord){ + const texture = textureRecord['texture'] as Texture; + + texture.wrapS = textureRecord.WrapS; + texture.wrapT = textureRecord.WrapT; + texture.anisotropy = 16; + texture.rotation = textureRecord.rotation; + texture.repeat.set(textureRecord.repeatX, textureRecord.repeatY); + texture.offset.set(textureRecord.moveX, textureRecord.moveY); + texture.needsUpdate = true; + +// for (let f of this.waits) +// f(); +// this.waits.length = 0; +// +// this.AsyncUpdated(); +} +/** + * 材质编辑器 + */ +export class MaterialEditor extends Singleton +{ + Geometrys: Map; + + CurGeometryName = ref("球"); + Canvas: HTMLElement; + ShowObject: Object3D; + ShowMesh: Mesh; + Viewer: Viewer; + + private _MeshMaterial: MeshPhysicalMaterial = new MeshPhysicalMaterial({}); + //构造 + private constructor() + { + super(); + this.initGeometrys(); + + this.LoadDefaultExr(); + this.LoadMetalEnv(); + } + + initGeometrys() + { + this.Geometrys = new Map( + [ + ["球", new SphereBufferGeometry(1000, 32, 32)], + ["圆环", new TorusBufferGeometry(0.8 * 1e3, 0.4 * 1e3, 32, 64)], + ["立方体", new BoxBufferGeometry(1e3, 1e3, 1e3, 1, 1, 1)], + ["环面纽结", new TorusKnotBufferGeometry(0.7 * 1e3, 0.3 * 1e3, 128, 64)], + ["圆锥体", new ConeBufferGeometry(1 * 1e3, 2 * 1e3, 32)], + // ["球(多面)", new SphereBufferGeometry(1 * 1e3, 64, 64)], + // ["立方体(多面)", new BoxBufferGeometry(1 * 1e3, 1 * 1e3, 1 * 1e3, 128, 128, 128)] + ] + ); + } + initViewer() + { + if (!this.Viewer) + { + this.Viewer = new Viewer(this.Canvas); + // this.Viewer.PreViewer.Cursor.CursorObject.visible = false; + // this.Viewer.CameraCtrl.CameraType = CameraType.PerspectiveCamera; + // this.Viewer.UsePass = false; + this.initScene(); + new MaterialEditorCamerControl(this.Viewer); + } + else + { + this.Canvas.appendChild(this.Viewer.Renderer.domElement); + } + + } + SetViewer(canvas: HTMLElement) + { + this.Canvas = canvas; + this.initViewer(); + this.CurGeometryName.value = "球"; + } + initScene() + { + let scene = this.Viewer.Scene; + this.ShowObject = new Object3D(); + let geo = this.Geometrys.get(this.CurGeometryName.value); + this.ShowMesh = new Mesh(geo, this._MeshMaterial); + this.ShowMesh.scale.set(1000, 1000, 1000); + + this.ShowObject.add(this.ShowMesh); + scene.add(this.ShowObject); + // let remove = autorun(() => + // { + // let geo = this.Geometrys.get(this.CurGeometryName.get()); + // if (geo) + // { + // this.ShowMesh.geometry = geo; + // this.Viewer.UpdateRender(); + // } + // }); + // end(this as MaterialEditor, this.dispose, remove); + + //环境光 + let ambient = new AmbientLight(); + ambient.intensity = 0.7; + scene.add(ambient); + } + + metaTexture: Texture; + metaPromise: Promise; + async LoadMetalEnv(): Promise + { + if (this.metaTexture) return this.metaTexture; + if (this.metaPromise) return this.metaPromise; + + return new Promise((res, rej) => + { + let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp']; + new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house/') + .load(urls, (t) => + { + t.encoding = sRGBEncoding; + t.mapping = CubeRefractionMapping; + + let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer); + let ldrCubeRenderTarget = pmremGenerator.fromCubemap(t); + + this.metaTexture = ldrCubeRenderTarget.texture; + res(this.metaTexture); + + this.Viewer.Scene.background = this.metaTexture; + // this.Viewer.Scene.background = new Color(255,0,0); + + this.Viewer.UpdateRender(); + }); + }); + } + + exrPromise: Promise; + exrTexture: Texture; + async LoadDefaultExr(): Promise + { + if (this.exrTexture) return this.exrTexture; + if (this.exrPromise) return this.exrPromise; + + this.exrPromise = new Promise((res, rej) => + { + let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp']; + new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house_gray/') + .load(urls, (t) => + { + t.encoding = sRGBEncoding; + t.mapping = CubeRefractionMapping; + let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer); + let target = pmremGenerator.fromCubemap(t); + this.exrTexture = target.texture; + res(this.exrTexture); + + this.Viewer.UpdateRender(); + }); + }); + return this.exrPromise; + } + + private Material: PhysicalMaterialRecord; + setMaterial(mat: PhysicalMaterialRecord) + { + this.Material = mat; + this._MeshMaterial.copy(mat.Material); + + let mtl = this._MeshMaterial; + if (mtl.metalness > 0.8) + this.LoadMetalEnv().then(env => + { + mtl.envMap = env; + mtl.needsUpdate = true; + }); + else + this.LoadDefaultExr().then(exr => + { + mtl.envMap = exr; + mtl.needsUpdate = true; + }); + + this.Update(); + } + + dispose() + { + } + + async Update() + { + let mat = this.ShowMesh.material as MeshPhysicalMaterial; + if (this.Material.map && this.Material.map.Object && this.Material.useMap) + { + let texture = this.Material.map.Object as TextureTableRecord; + await textureRenderUpdate(texture); + } + if (this.Material.bumpMap && this.Material.bumpMap.Object) + { + let texture = this.Material.bumpMap.Object as TextureTableRecord; + await textureRenderUpdate(texture); + } + if (this.Material.roughnessMap && this.Material.roughnessMap.Object) + { + let texture = this.Material.roughnessMap.Object as TextureTableRecord; + await textureRenderUpdate(texture); + } + mat.needsUpdate = true; + + this._MeshMaterial.copy(this.Material.Material); + + let mtl = this._MeshMaterial; + if (mtl.metalness > 0.8) + this.LoadMetalEnv().then(env => + { + mtl.envMap = env; + mtl.needsUpdate = true; + }); + else + this.LoadDefaultExr().then(exr => + { + mtl.envMap = exr; + mtl.needsUpdate = true; + }); + + this.Viewer.UpdateRender(); + } +} diff --git a/src/common/MaterialMouseControl.ts b/src/common/MaterialMouseControl.ts new file mode 100644 index 0000000..002805e --- /dev/null +++ b/src/common/MaterialMouseControl.ts @@ -0,0 +1,85 @@ +import { Vector3 } from 'three'; +import type { Viewer } from './Viewer'; + +/** + * 材质编辑器的场景鼠标控制. + */ +export class MaterialEditorCamerControl +{ + private Viewer: Viewer; + + //State. + private _MouseIsDown: boolean = false; + private _StartPoint: Vector3 = new Vector3(); + private _EndPoint = new Vector3(); + private pointId: number; + + constructor(view: Viewer) + { + this.Viewer = view; + + this.initMouseControl(); + } + initMouseControl() + { + let el = this.Viewer.Renderer.domElement; + el.addEventListener("pointerdown", (e) => { this.pointId = e.pointerId; }, false); + el.addEventListener("mousedown", this.onMouseDown, false); + el.addEventListener("mousemove", this.onMouseMove, false); + el.addEventListener("mouseup", this.onMouseUp, false); + el.addEventListener('wheel', this.onMouseWheel, false); + } + onMouseDown = (event: MouseEvent) => + { + this.requestPointerLock(); + this._MouseIsDown = true; + this._StartPoint.set(event.offsetX, event.offsetY, 0); + }; + onMouseUp = (event: MouseEvent) => + { + this._MouseIsDown = false; + this.exitPointerLock(); + }; + onMouseMove = (event: MouseEvent) => + { + event.preventDefault(); + if (this._MouseIsDown) + { + this._EndPoint.set(event.offsetX, event.offsetY, 0); + let changeVec: Vector3 = new Vector3(); + changeVec.subVectors(this._EndPoint, this._StartPoint); + this._StartPoint.copy(this._EndPoint); + + this.Viewer.Rotate(changeVec); + } + }; + onMouseWheel = (event: WheelEvent) => + { + if (event.deltaY < 0) + { + this.Viewer.Zoom(0.6); + } + else if (event.deltaY > 0) + { + this.Viewer.Zoom(1.4); + } + }; + + requestPointerLock() + { + if (this.Viewer.Renderer.domElement.setPointerCapture) + this.Viewer.Renderer.domElement.setPointerCapture(this.pointId); + } + + exitPointerLock() + { + if (this.Viewer.Renderer.domElement.releasePointerCapture && this.pointId !== undefined) + { + try + { + this.Viewer.Renderer.domElement.releasePointerCapture(this.pointId); + } + catch (error) { } + } + } +} diff --git a/src/common/Matrix2.ts b/src/common/Matrix2.ts new file mode 100644 index 0000000..f42a76a --- /dev/null +++ b/src/common/Matrix2.ts @@ -0,0 +1,33 @@ +import { Vector2, Vector3 } from "three"; + +export class Matrix2 +{ + private el = [1, 0, 0, 1]; + set(n11: number, n12: number, n21: number, n22: number) + { + let te = this.el; + + te[0] = n11; te[1] = n21; + te[2] = n12; te[3] = n22; + return this; + } + applyVector(vec: Vector2 | Vector3) + { + let x = vec.x, y = vec.y; + let e = this.el; + vec.x = e[0] * x + e[2] * y; + vec.y = e[1] * x + e[3] * y; + + return this; + } + + setRotate(theta: number): Matrix2 + { + // let el = this.el; + let c = Math.cos(theta), s = Math.sin(theta); + + this.set(c, -s, + s, c); + return this; + } +} diff --git a/src/common/PMREMGenerator2.ts b/src/common/PMREMGenerator2.ts new file mode 100644 index 0000000..aea52f5 --- /dev/null +++ b/src/common/PMREMGenerator2.ts @@ -0,0 +1,997 @@ +import { BufferAttribute, BufferGeometry, CubeUVReflectionMapping, GammaEncoding, LinearEncoding, Mesh, NearestFilter, NoBlending, NoToneMapping, OrthographicCamera, PerspectiveCamera, RawShaderMaterial, RGBDEncoding, RGBEEncoding, RGBEFormat, RGBM16Encoding, RGBM7Encoding, sRGBEncoding, UnsignedByteType, Vector2, Vector3, WebGLRenderer, WebGLRenderTarget } from "three"; + +const LOD_MIN = 4; +const LOD_MAX = 8; +const SIZE_MAX = Math.pow(2, LOD_MAX); + +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [0.125, 0.215, 0.35, 0.446, 0.526, 0.582]; + +const TOTAL_LODS = LOD_MAX - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; + +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; + +const ENCODINGS = { + [LinearEncoding]: 0, + [sRGBEncoding]: 1, + [RGBEEncoding]: 2, + [RGBM7Encoding]: 3, + [RGBM16Encoding]: 4, + [RGBDEncoding]: 5, + [GammaEncoding]: 6 +}; + +//@ts-ignore +const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); +const { _lodPlanes, _sizeLods, _sigmas } = /*@__PURE__*/ _createPlanes(); +let _oldTarget = null; + +// Golden Ratio +const PHI = (1 + Math.sqrt(5)) / 2; +const INV_PHI = 1 / PHI; + +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3(1, 1, 1), + /*@__PURE__*/ new Vector3(- 1, 1, 1), + /*@__PURE__*/ new Vector3(1, 1, - 1), + /*@__PURE__*/ new Vector3(- 1, 1, - 1), + /*@__PURE__*/ new Vector3(0, PHI, INV_PHI), + /*@__PURE__*/ new Vector3(0, PHI, - INV_PHI), + /*@__PURE__*/ new Vector3(INV_PHI, 0, PHI), + /*@__PURE__*/ new Vector3(- INV_PHI, 0, PHI), + /*@__PURE__*/ new Vector3(PHI, INV_PHI, 0), + /*@__PURE__*/ new Vector3(- PHI, INV_PHI, 0)]; + +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + */ + +export class PMREMGenerator3 +{ + _renderer: WebGLRenderer; + _pingPongRenderTarget: any; + _blurMaterial: RawShaderMaterial; + _equirectShader: any; + _cubemapShader: any; + + constructor(renderer: WebGLRenderer) + { + + this._renderer = renderer; + this._pingPongRenderTarget = null; + + this._blurMaterial = _getBlurShader(MAX_SAMPLES); + this._equirectShader = null; + this._cubemapShader = null; + + this._compileMaterial(this._blurMaterial); + + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety (the cubeCamera + * is placed at the origin). + */ + fromScene(scene, sigma = 0, near = 0.1, far = 100) + { + + _oldTarget = this._renderer.getRenderTarget(); + const cubeUVRenderTarget = this._allocateTargets(); + + this._sceneToCubeUV(scene, near, far, cubeUVRenderTarget); + if (sigma > 0) + { + + this._blur(cubeUVRenderTarget, 0, 0, sigma); + + } + + this._applyPMREM(cubeUVRenderTarget); + this._cleanup(cubeUVRenderTarget); + + return cubeUVRenderTarget; + + } + + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * (RGBFormat) or HDR (RGBEFormat). The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + */ + fromEquirectangular(equirectangular) + { + + return this._fromTexture(equirectangular); + + } + + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * (RGBFormat) or HDR (RGBEFormat). The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + */ + fromCubemap(cubemap) + { + + return this._fromTexture(cubemap); + + } + + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileCubemapShader() + { + + if (this._cubemapShader === null) + { + + this._cubemapShader = _getCubemapShader(); + this._compileMaterial(this._cubemapShader); + + } + + } + + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileEquirectangularShader() + { + + if (this._equirectShader === null) + { + + this._equirectShader = _getEquirectShader(); + this._compileMaterial(this._equirectShader); + + } + + } + + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() + { + + this._blurMaterial.dispose(); + + if (this._cubemapShader !== null) this._cubemapShader.dispose(); + if (this._equirectShader !== null) this._equirectShader.dispose(); + + for (let i = 0; i < _lodPlanes.length; i++) + { + + _lodPlanes[i].dispose(); + + } + + } + + // private interface + + _cleanup(outputTarget) + { + + this._pingPongRenderTarget.dispose(); + this._renderer.setRenderTarget(_oldTarget); + outputTarget.scissorTest = false; + _setViewport(outputTarget, 0, 0, outputTarget.width, outputTarget.height); + + } + + _fromTexture(texture) + { + + _oldTarget = this._renderer.getRenderTarget(); + const cubeUVRenderTarget = this._allocateTargets(texture); + this._textureToCubeUV(texture, cubeUVRenderTarget); + this._applyPMREM(cubeUVRenderTarget); + this._cleanup(cubeUVRenderTarget); + + return cubeUVRenderTarget; + + } + + _allocateTargets(texture?) + { // warning: null texture is valid + + const params = { + magFilter: NearestFilter, + minFilter: NearestFilter, + generateMipmaps: false, + type: UnsignedByteType, + format: RGBEFormat, + encoding: _isLDR(texture) ? texture.encoding : RGBEEncoding, + depthBuffer: false + }; + + const cubeUVRenderTarget = _createRenderTarget(params); + cubeUVRenderTarget.depthBuffer = texture ? false : true; + this._pingPongRenderTarget = _createRenderTarget(params); + return cubeUVRenderTarget; + + } + + _compileMaterial(material) + { + + const tmpMesh = new Mesh(_lodPlanes[0], material); + this._renderer.compile(tmpMesh, _flatCamera); + + } + + _sceneToCubeUV(scene, near, far, cubeUVRenderTarget) + { + + const fov = 90; + const aspect = 1; + const cubeCamera = new PerspectiveCamera(fov, aspect, near, far); + const upSign = [1, - 1, 1, 1, 1, 1]; + const forwardSign = [1, 1, 1, - 1, - 1, - 1]; + const renderer = this._renderer; + + const outputEncoding = renderer.outputEncoding; + const toneMapping = renderer.toneMapping; + const clearColor = renderer.getClearColor(); + const clearAlpha = renderer.getClearAlpha(); + + renderer.toneMapping = NoToneMapping; + renderer.outputEncoding = LinearEncoding; + + let background = scene.background; + if (background && background.isColor) + { + + background.convertSRGBToLinear(); + // Convert linear to RGBE + const maxComponent = Math.max(background.r, background.g, background.b); + const fExp = Math.min(Math.max(Math.ceil(Math.log2(maxComponent)), - 128.0), 127.0); + background = background.multiplyScalar(Math.pow(2.0, - fExp)); + const alpha = (fExp + 128.0) / 255.0; + renderer.setClearColor(background, alpha); + scene.background = null; + + } + + for (let i = 0; i < 6; i++) + { + + const col = i % 3; + if (col == 0) + { + + cubeCamera.up.set(0, upSign[i], 0); + cubeCamera.lookAt(forwardSign[i], 0, 0); + + } else if (col == 1) + { + + cubeCamera.up.set(0, 0, upSign[i]); + cubeCamera.lookAt(0, forwardSign[i], 0); + + } else + { + + cubeCamera.up.set(0, upSign[i], 0); + cubeCamera.lookAt(0, 0, forwardSign[i]); + + } + + _setViewport(cubeUVRenderTarget, + col * SIZE_MAX, i > 2 ? SIZE_MAX : 0, SIZE_MAX, SIZE_MAX); + renderer.setRenderTarget(cubeUVRenderTarget); + renderer.render(scene, cubeCamera); + + } + + renderer.toneMapping = toneMapping; + renderer.outputEncoding = outputEncoding; + renderer.setClearColor(clearColor, clearAlpha); + + } + + _textureToCubeUV(texture, cubeUVRenderTarget) + { + + const renderer = this._renderer; + + if (texture.isCubeTexture) + { + + if (this._cubemapShader == null) + { + + this._cubemapShader = _getCubemapShader(); + + } + + } else + { + + if (this._equirectShader == null) + { + + this._equirectShader = _getEquirectShader(); + + } + + } + + const material = texture.isCubeTexture ? this._cubemapShader : this._equirectShader; + const mesh = new Mesh(_lodPlanes[0], material); + + const uniforms = material.uniforms; + + uniforms['envMap'].value = texture; + + if (!texture.isCubeTexture) + { + + uniforms['texelSize'].value.set(1.0 / texture.image.width, 1.0 / texture.image.height); + + } + + uniforms['inputEncoding'].value = ENCODINGS[texture.encoding]; + uniforms['outputEncoding'].value = ENCODINGS[cubeUVRenderTarget.texture.encoding]; + + _setViewport(cubeUVRenderTarget, 0, 0, 3 * SIZE_MAX, 2 * SIZE_MAX); + + renderer.setRenderTarget(cubeUVRenderTarget); + renderer.render(mesh, _flatCamera); + + } + + _applyPMREM(cubeUVRenderTarget) + { + + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + + for (let i = 1; i < TOTAL_LODS; i++) + { + + const sigma = Math.sqrt(_sigmas[i] * _sigmas[i] - _sigmas[i - 1] * _sigmas[i - 1]); + + const poleAxis = _axisDirections[(i - 1) % _axisDirections.length]; + + this._blur(cubeUVRenderTarget, i - 1, i, sigma, poleAxis); + + } + + renderer.autoClear = autoClear; + + } + + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + */ + _blur(cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis?) + { + + const pingPongRenderTarget = this._pingPongRenderTarget; + + this._halfBlur( + cubeUVRenderTarget, + pingPongRenderTarget, + lodIn, + lodOut, + sigma, + 'latitudinal', + poleAxis); + + this._halfBlur( + pingPongRenderTarget, + cubeUVRenderTarget, + lodOut, + lodOut, + sigma, + 'longitudinal', + poleAxis); + + } + + _halfBlur(targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis) + { + + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; + + if (direction !== 'latitudinal' && direction !== 'longitudinal') + { + + console.error( + 'blur direction must be either latitudinal or longitudinal!'); + + } + + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; + + const blurMesh = new Mesh(_lodPlanes[lodOut], blurMaterial); + const blurUniforms = blurMaterial.uniforms; + + const pixels = _sizeLods[lodIn] - 1; + const radiansPerPixel = isFinite(sigmaRadians) ? Math.PI / (2 * pixels) : 2 * Math.PI / (2 * MAX_SAMPLES - 1); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite(sigmaRadians) ? 1 + Math.floor(STANDARD_DEVIATIONS * sigmaPixels) : MAX_SAMPLES; + + if (samples > MAX_SAMPLES) + { + + console.warn(`sigmaRadians, ${sigmaRadians}, is too large and will clip, as it requested ${samples} samples when the maximum is set to ${MAX_SAMPLES}`); + + } + + const weights = []; + let sum = 0; + + for (let i = 0; i < MAX_SAMPLES; ++i) + { + + const x = i / sigmaPixels; + const weight = Math.exp(- x * x / 2); + weights.push(weight); + + if (i == 0) + { + + sum += weight; + + } else if (i < samples) + { + + sum += 2 * weight; + + } + + } + + for (let i = 0; i < weights.length; i++) + { + + weights[i] = weights[i] / sum; + + } + + blurUniforms['envMap'].value = targetIn.texture; + blurUniforms['samples'].value = samples; + blurUniforms['weights'].value = weights; + blurUniforms['latitudinal'].value = direction === 'latitudinal'; + + if (poleAxis) + { + + blurUniforms['poleAxis'].value = poleAxis; + + } + + blurUniforms['dTheta'].value = radiansPerPixel; + blurUniforms['mipInt'].value = LOD_MAX - lodIn; + blurUniforms['inputEncoding'].value = ENCODINGS[targetIn.texture.encoding]; + blurUniforms['outputEncoding'].value = ENCODINGS[targetIn.texture.encoding]; + + const outputSize = _sizeLods[lodOut]; + const x = 3 * Math.max(0, SIZE_MAX - 2 * outputSize); + const y = (lodOut === 0 ? 0 : 2 * SIZE_MAX) + 2 * outputSize * (lodOut > LOD_MAX - LOD_MIN ? lodOut - LOD_MAX + LOD_MIN : 0); + + _setViewport(targetOut, x, y, 3 * outputSize, 2 * outputSize); + renderer.setRenderTarget(targetOut); + renderer.render(blurMesh, _flatCamera); + + } + +} + +function _isLDR(texture) +{ + + if (texture === undefined || texture.type !== UnsignedByteType) return false; + + return texture.encoding === LinearEncoding || texture.encoding === sRGBEncoding || texture.encoding === GammaEncoding; + +} + +function _createPlanes() +{ + + const _lodPlanes = []; + const _sizeLods = []; + const _sigmas = []; + + let lod = LOD_MAX; + + for (let i = 0; i < TOTAL_LODS; i++) + { + + const sizeLod = Math.pow(2, lod); + _sizeLods.push(sizeLod); + let sigma = 1.0 / sizeLod; + + if (i > LOD_MAX - LOD_MIN) + { + + sigma = EXTRA_LOD_SIGMA[i - LOD_MAX + LOD_MIN - 1]; + + } else if (i == 0) + { + + sigma = 0; + + } + + _sigmas.push(sigma); + + const texelSize = 1.0 / (sizeLod - 1); + const min = - texelSize / 2; + const max = 1 + texelSize / 2; + const uv1 = [min, min, max, min, max, max, min, min, max, max, min, max]; + + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; + + const position = new Float32Array(positionSize * vertices * cubeFaces); + const uv = new Float32Array(uvSize * vertices * cubeFaces); + const faceIndex = new Float32Array(faceIndexSize * vertices * cubeFaces); + + for (let face = 0; face < cubeFaces; face++) + { + + const x = (face % 3) * 2 / 3 - 1; + const y = face > 2 ? 0 : - 1; + const coordinates = [ + x, y, 0, + x + 2 / 3, y, 0, + x + 2 / 3, y + 1, 0, + x, y, 0, + x + 2 / 3, y + 1, 0, + x, y + 1, 0 + ]; + position.set(coordinates, positionSize * vertices * face); + uv.set(uv1, uvSize * vertices * face); + const fill = [face, face, face, face, face, face]; + faceIndex.set(fill, faceIndexSize * vertices * face); + + } + + const planes = new BufferGeometry(); + planes.setAttribute('position', new BufferAttribute(position, positionSize)); + planes.setAttribute('uv', new BufferAttribute(uv, uvSize)); + planes.setAttribute('faceIndex', new BufferAttribute(faceIndex, faceIndexSize)); + _lodPlanes.push(planes); + + if (lod > LOD_MIN) + { + + lod--; + + } + + } + + return { _lodPlanes, _sizeLods, _sigmas }; + +} + +function _createRenderTarget(params) +{ + + const cubeUVRenderTarget = new WebGLRenderTarget(3 * SIZE_MAX, 3 * SIZE_MAX, params); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; + +} + +function _setViewport(target, x, y, width, height) +{ + + target.viewport.set(x, y, width, height); + target.scissor.set(x, y, width, height); + +} + +function _getBlurShader(maxSamples) +{ + + const weights = new Float32Array(maxSamples); + const poleAxis = new Vector3(0, 1, 0); + const shaderMaterial = new RawShaderMaterial({ + + name: 'SphericalGaussianBlur', + + defines: { 'n': maxSamples }, + + uniforms: { + 'envMap': { value: null }, + 'samples': { value: 1 }, + 'weights': { value: weights }, + 'latitudinal': { value: false }, + 'dTheta': { value: 0 }, + 'mipInt': { value: 0 }, + 'poleAxis': { value: poleAxis }, + 'inputEncoding': { value: ENCODINGS[LinearEncoding] }, + 'outputEncoding': { value: ENCODINGS[LinearEncoding] } + }, + + vertexShader: _getCommonVertexShader(), + + fragmentShader: /* glsl */` + + precision mediump float; + precision mediump int; + + varying vec3 vOutputDirection; + + uniform sampler2D envMap; + uniform int samples; + uniform float weights[ n ]; + uniform bool latitudinal; + uniform float dTheta; + uniform float mipInt; + uniform vec3 poleAxis; + + ${_getEncodings()} + + #define ENVMAP_TYPE_CUBE_UV + #include + + vec3 getSample( float theta, vec3 axis ) { + + float cosTheta = cos( theta ); + // Rodrigues' axis-angle rotation + vec3 sampleDirection = vOutputDirection * cosTheta + + cross( axis, vOutputDirection ) * sin( theta ) + + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); + + return bilinearCubeUV( envMap, sampleDirection, mipInt ); + + } + + void main() { + + vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); + + if ( all( equal( axis, vec3( 0.0 ) ) ) ) { + + axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); + + } + + axis = normalize( axis ); + + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); + + for ( int i = 1; i < n; i++ ) { + + if ( i >= samples ) { + + break; + + } + + float theta = dTheta * float( i ); + gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); + gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); + + } + + gl_FragColor = linearToOutputTexel( gl_FragColor ); + + } + `, + + blending: NoBlending, + depthTest: false, + depthWrite: false + + }); + + return shaderMaterial; + +} + +function _getEquirectShader() +{ + + const texelSize = new Vector2(1, 1); + const shaderMaterial = new RawShaderMaterial({ + + name: 'EquirectangularToCubeUV', + + uniforms: { + 'envMap': { value: null }, + 'texelSize': { value: texelSize }, + 'inputEncoding': { value: ENCODINGS[LinearEncoding] }, + 'outputEncoding': { value: ENCODINGS[LinearEncoding] } + }, + + vertexShader: _getCommonVertexShader(), + + fragmentShader: /* glsl */` + + precision mediump float; + precision mediump int; + + varying vec3 vOutputDirection; + + uniform sampler2D envMap; + uniform vec2 texelSize; + + ${_getEncodings()} + + #include + + void main() { + + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + + vec3 outputDirection = normalize( vOutputDirection ); + vec2 uv = equirectUv( outputDirection ); + + vec2 f = fract( uv / texelSize - 0.5 ); + uv -= f * texelSize; + vec3 tl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb; + uv.x += texelSize.x; + vec3 tr = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb; + uv.y += texelSize.y; + vec3 br = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb; + uv.x -= texelSize.x; + vec3 bl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb; + + vec3 tm = mix( tl, tr, f.x ); + vec3 bm = mix( bl, br, f.x ); + gl_FragColor.rgb = mix( tm, bm, f.y ); + + gl_FragColor = linearToOutputTexel( gl_FragColor ); + + } + `, + + blending: NoBlending, + depthTest: false, + depthWrite: false + + }); + + return shaderMaterial; + +} + +function _getCubemapShader() +{ + + const shaderMaterial = new RawShaderMaterial({ + + name: 'CubemapToCubeUV', + + uniforms: { + 'envMap': { value: null }, + 'inputEncoding': { value: ENCODINGS[LinearEncoding] }, + 'outputEncoding': { value: ENCODINGS[LinearEncoding] } + }, + + vertexShader: _getCommonVertexShader(), + + fragmentShader: /* glsl */` + + precision mediump float; + precision mediump int; + + varying vec3 vOutputDirection; + + uniform samplerCube envMap; + + ${_getEncodings()} + + void main() { + + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + gl_FragColor.rgb = envMapTexelToLinear( textureCube( envMap, vec3( - vOutputDirection.x, vOutputDirection.yz ) ) ).rgb; + gl_FragColor = linearToOutputTexel( gl_FragColor ); + + } + `, + + blending: NoBlending, + depthTest: false, + depthWrite: false + + }); + + return shaderMaterial; + +} + +function _getCommonVertexShader() +{ + + return /* glsl */` + + precision mediump float; + precision mediump int; + + attribute vec3 position; + attribute vec2 uv; + attribute float faceIndex; + + varying vec3 vOutputDirection; + + // RH coordinate system; PMREM face-indexing convention + vec3 getDirection( vec2 uv, float face ) { + + uv = 2.0 * uv - 1.0; + + vec3 direction = vec3( uv, 1.0 ); + + if ( face == 0.0 ) { + + direction = direction.zyx; // ( 1, v, u ) pos x + + } else if ( face == 1.0 ) { + + direction = direction.xzy; + direction.xz *= -1.0; // ( -u, 1, -v ) pos y + + } else if ( face == 2.0 ) { + + direction.x *= -1.0; // ( -u, v, 1 ) pos z + + } else if ( face == 3.0 ) { + + direction = direction.zyx; + direction.xz *= -1.0; // ( -1, v, -u ) neg x + + } else if ( face == 4.0 ) { + + direction = direction.xzy; + direction.xy *= -1.0; // ( -u, -1, v ) neg y + + } else if ( face == 5.0 ) { + + direction.z *= -1.0; // ( u, v, -1 ) neg z + + } + + return direction; + + } + + void main() { + + vOutputDirection = getDirection( uv, faceIndex ); + + // //从xz->z-up坐标系变换到 threejs坐标系 + mat3 ro = mat3( + 1.0, 0.0, 0.0, + 0.0, 0.0, -1.0, + 0.0, 1.0, 0 + ); + vOutputDirection = ro * vOutputDirection; + + gl_Position = vec4( position, 1.0 ); + + } + `; + +} + +function _getEncodings() +{ + + return /* glsl */` + + uniform int inputEncoding; + uniform int outputEncoding; + + #include + + vec4 inputTexelToLinear( vec4 value ) { + + if ( inputEncoding == 0 ) { + + return value; + + } else if ( inputEncoding == 1 ) { + + return sRGBToLinear( value ); + + } else if ( inputEncoding == 2 ) { + + return RGBEToLinear( value ); + + } else if ( inputEncoding == 3 ) { + + return RGBMToLinear( value, 7.0 ); + + } else if ( inputEncoding == 4 ) { + + return RGBMToLinear( value, 16.0 ); + + } else if ( inputEncoding == 5 ) { + + return RGBDToLinear( value, 256.0 ); + + } else { + + return GammaToLinear( value, 2.2 ); + + } + + } + + vec4 linearToOutputTexel( vec4 value ) { + + if ( outputEncoding == 0 ) { + + return value; + + } else if ( outputEncoding == 1 ) { + + return LinearTosRGB( value ); + + } else if ( outputEncoding == 2 ) { + + return LinearToRGBE( value ); + + } else if ( outputEncoding == 3 ) { + + return LinearToRGBM( value, 7.0 ); + + } else if ( outputEncoding == 4 ) { + + return LinearToRGBM( value, 16.0 ); + + } else if ( outputEncoding == 5 ) { + + return LinearToRGBD( value, 256.0 ); + + } else { + + return LinearToGamma( value, 2.2 ); + + } + + } + + vec4 envMapTexelToLinear( vec4 color ) { + + return inputTexelToLinear( color ); + + } + `; + +} diff --git a/src/common/PlaneExt.ts b/src/common/PlaneExt.ts new file mode 100644 index 0000000..5a6ecff --- /dev/null +++ b/src/common/PlaneExt.ts @@ -0,0 +1,46 @@ +import * as THREE from 'three'; +import { Vector3, Line3, Plane } from "three"; + +export class PlaneExt extends Plane +{ + constructor(normal?: Vector3, constant?: number) + { + super(normal, constant); + } + intersectLine(line: Line3, optionalTarget?: Vector3, extendLine?: boolean): Vector3 + { + let v1 = new Vector3(); + + let result = optionalTarget || new Vector3(); + + let direction = line.delta(v1); + + let denominator = this.normal.dot(direction); + + if (denominator === 0) + { + // line is coplanar, return origin + if (this.distanceToPoint(line.start) === 0) + { + return result.copy(line.start); + } + // Unsure if this is the correct method to handle this case. + return undefined; + } + + let t = - (line.start.dot(this.normal) + this.constant) / denominator; + //If you not extendLine,check intersect point in Line + if (!extendLine && (t < 0 || t > 1)) + { + return undefined; + } + + return result.copy(direction).multiplyScalar(t).add(line.start); + } + intersectRay(ray: THREE.Ray, optionalTarget?: Vector3, extendLine?: boolean): Vector3 + { + // 从射线初始位置 + let line = new THREE.Line3(ray.origin.clone(), ray.origin.clone().add(ray.direction)); + return this.intersectLine(line, optionalTarget, extendLine); + } +} diff --git a/src/common/Singleton.ts b/src/common/Singleton.ts new file mode 100644 index 0000000..73d356a --- /dev/null +++ b/src/common/Singleton.ts @@ -0,0 +1,40 @@ + + +let instanceMap = new Map(); + +export interface PrototypeType extends Function +{ + prototype: T; +} + +export interface ConstructorFunctionType extends PrototypeType +{ + new(...args: any[]): T; +} + +export type ConstructorType = PrototypeType> = (ConstructorFunctionType | PrototypeType) & { + [Key in keyof Static]: Static[Key]; +}; + +/** + * 构造单例类的静态类. + * # Example: + * class A extends Singleton(){}; + * //获得单例 + * let a = A.GetInstance(); + */ +export class Singleton +{ + protected constructor() { } + + //ref:https://github.com/Microsoft/TypeScript/issues/5863 + static GetInstance(this: ConstructorType): T + { + if (instanceMap.has(this)) + return instanceMap.get(this); + //@ts-ignore + let __instance__ = new this.prototype.constructor(); + instanceMap.set(this, __instance__); + return __instance__; + } +} diff --git a/src/common/Viewer.ts b/src/common/Viewer.ts new file mode 100644 index 0000000..8810967 --- /dev/null +++ b/src/common/Viewer.ts @@ -0,0 +1,168 @@ +import { Raycaster, Scene, Vector3, WebGLRenderer, type WebGLRendererParameters } from "three"; +import { cZeroVec, GetBox, GetBoxArr } from "./GeUtils"; +import { PlaneExt } from "./PlaneExt"; +import { CameraUpdate } from "webcad_ue4_api"; +import { CameraControls } from "./CameraControls"; + +export class Viewer { + m_LookTarget: any; + m_Camera: CameraUpdate = new CameraUpdate(); + CameraCtrl: CameraControls; + protected NeedUpdate: boolean = true; + Renderer: WebGLRenderer;//渲染器 //暂时只用这个类型 + m_DomEl: HTMLElement; //画布容器 + + _Height: number = 0; + _Width: number = 0; + + _Scene: Scene = new Scene(); + + get Scene() { + return this._Scene; + } + + UpdateRender() + { + this.NeedUpdate = true; + } + + /** + * + * @param {HTMLElement} canvasContainer 可以传入一个div或者一个画布 + * @memberof Viewer + */ + constructor(canvasContainer: HTMLElement) { + + this.m_DomEl = canvasContainer; + this.initRender(canvasContainer); + this.OnSize(); + this.StartRender(); + this.CameraCtrl = new CameraControls(this); + window.addEventListener("resize", () => { + this.OnSize(); + }); + } + + //初始化render + initRender(canvasContainer: HTMLElement) { + let params: WebGLRendererParameters = { + antialias: true,//antialias:true/false是否开启反锯齿 + precision: "highp",//precision:highp/mediump/lowp着色精度选择 + alpha: true//alpha:true/false是否可以设置背景色透明 + }; + this.Renderer = new WebGLRenderer(params); + //加到画布 + canvasContainer.appendChild(this.Renderer.domElement); + + this.Renderer.autoClear = true; + + //如果设置,那么它希望所有的纹理和颜色都是预乘的伽玛。默认值为false。 + // this.Renderer.gammaInput = true; + // this.Renderer.gammaOutput = true; + // this.Renderer.shadowMap.enabled = true; + // this.Renderer.toneMapping = ReinhardToneMapping; + //设置设备像素比。 这通常用于HiDPI设备,以防止模糊输出画布。 + this.Renderer.setPixelRatio(window.devicePixelRatio); + this.Renderer.physicallyCorrectLights = true; + //this.Renderer.toneMappingExposure = Math.pow(1, 5.0); // to allow for very bright scenes. + + //设置它的背景色为黑色 + this.Renderer.setClearColor(0xffffff, 1); + + + this.OnSize(); + } + + OnSize = (width?: number, height?: number) => { + this._Width = width ? width : this.m_DomEl.clientWidth; + this._Height = height ? height : this.m_DomEl.clientHeight; + + //校验.成为2的倍数 避免外轮廓错误. + if (this._Width % 2 == 1) + this._Width -= 1; + if (this._Height % 2 == 1) + this._Height -= 1; + + this.Renderer.setSize(this._Width, this._Height); + this.m_Camera.SetSize(this._Width, this._Height); + }; + + StartRender = () => { + requestAnimationFrame(this.StartRender); + if (this._Scene != null && this.NeedUpdate) { + this.Render(); + this.NeedUpdate = false; + } + }; + Render() { + this.Renderer.render(this._Scene, this.m_Camera.Camera); + } + + ScreenToWorld(pt: Vector3, planVec?: Vector3) { + //变换和求交点 + let plan = new PlaneExt(planVec || new Vector3(0, 0, 1)); + let raycaster = new Raycaster(); + // 射线从相机射线向屏幕点位置 + raycaster.setFromCamera( + { + x: (pt.x / this._Width) * 2 - 1, + y: - (pt.y / this._Height) * 2 + 1 + } + , this.m_Camera.Camera + ); + plan.intersectRay(raycaster.ray, pt, true); + } + WorldToScreen(pt: Vector3) { + let widthHalf = this._Width * 0.5; + let heightHalf = this._Height * 0.5; + + pt.project(this.m_Camera.Camera); + + pt.x = (pt.x * widthHalf) + widthHalf; + pt.y = - (pt.y * heightHalf) + heightHalf; + } + + /** + * 更新视角观测目标(物体中心) + * + * @memberof Viewer + */ + UpdateLockTarget() { + let renderList = this.Renderer.renderLists.get(this._Scene, this.m_Camera.Camera); + let box = GetBoxArr(renderList.opaque.map(o => o.object)); + if (box) + this.m_LookTarget = box.getCenter(new Vector3()); + else + this.m_LookTarget = cZeroVec; + } + Rotate(mouseMove: Vector3) { + this.m_Camera.Rotate(mouseMove, this.m_LookTarget); + this.NeedUpdate = true; + } + Pan(mouseMove: Vector3) { + this.m_Camera.Pan(mouseMove); + this.NeedUpdate = true; + } + Zoom(scale: number, center?: Vector3) { + this.m_Camera.Zoom(scale, center); + this.NeedUpdate = true; + } + ZoomAll() + { + this.m_Camera.ZoomExtentsBox3(GetBox(this._Scene, true)); + this.NeedUpdate = true; + } + + ViewToTop() { + this.m_Camera.LookAt(new Vector3(0, 0, -1)); + this.NeedUpdate = true; + } + ViewToFront() { + this.m_Camera.LookAt(new Vector3(0, 1, 0)); + this.NeedUpdate = true; + } + ViewToSwiso() { + this.m_Camera.LookAt(new Vector3(1, 1, -1)); + this.NeedUpdate = true; + } +} diff --git a/src/components/MaterialView.vue b/src/components/MaterialView.vue new file mode 100644 index 0000000..e2759e7 --- /dev/null +++ b/src/components/MaterialView.vue @@ -0,0 +1,41 @@ + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..bf50ae6 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import { createApp } from 'vue' + +import App from './App.vue' + +createApp(App).mount('#app') + diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..069c9cf --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + /* Linting */ + "strict": false, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..bbcf80c --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +})