commit 18eaaff65fe2fec38ca28a197b0b4a8af47eab97 Author: 4hyhzi <2082529121@qq.com> Date: Mon May 18 16:51:56 2026 +0800 chore: 初始化glTF模型压缩工具包 添加了完整的项目配置、工具函数、纹理压缩与模型优化实现,以及文档说明 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c359bee --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dist +demo +node_modules \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..c3f7e9c --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +registry=https://npm.blendercg.art/ +always-auth=true +//npm.blendercg.art/:_authToken=MjVhMDVlN2ZiODUxMjFkNjhmYzVhZWFmOTZhNTA3Mzc6ZjkyNTYxN2RhM2QwOTc5NTFiMTU3MGEyM2U3Zjc0NzQ4YmUzYTJiOTc1Yzg0OWUwMzEzNjgyZDAwMjdlNDA2ZmZkYjJmODFhYWQ2MjVjYmFlZGJj \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2c0cfb --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# @g1sir8b/gltf-model-compressor + +用于: + +- 优化 `.gltf` / `.glb` +- 自动补全法线和切线 +- 可选纹理压缩 +- 输出为 `gltf + buffer.bin` 或 `glb` + +## 构建 + +```bash +npm run build +``` + +## 使用 + +```ts +import optimizeModel from '@g1sir8b/gltf-model-compressor'; + +await optimizeModel( + 'D:/input/model.glb', + 'D:/output', + 'medium', + true +); +``` + +也支持对象参数: + +```ts +import { optimizeModelWithOptions } from '@g1sir8b/gltf-model-compressor'; + +await optimizeModelWithOptions({ + input: 'D:/input/model.gltf', + output: 'D:/output', + compressTex: 'off', + toGLB: false +}); +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3ad12f7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1458 @@ +{ + "name": "@g1sir8b/gltf-model-compressor", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@g1sir8b/gltf-model-compressor", + "version": "1.0.0", + "dependencies": { + "gl-matrix": "^3.4.3", + "gltf-pipeline": "^4.1.0", + "image-size": "^2.0.2", + "jimp": "^1.6.1", + "mime": "^3.0.0", + "seinjs-texture-compressor": "^1.0.3" + }, + "devDependencies": { + "@types/mime": "^3.0.0", + "@types/node": "^20.0.0", + "typescript": "^5.2.2" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://npm.blendercg.art/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@cesium/engine": { + "version": "24.0.0", + "resolved": "https://npm.blendercg.art/@cesium/engine/-/engine-24.0.0.tgz", + "integrity": "sha512-zJ2gl0tyw/FFhBtvp6UYw+0JQJb2J9EiTJYvVSndc6+6qPR5GHLFzFjA1msLLxucfcpc7uI9R2pXNEPluheR/g==", + "license": "Apache-2.0", + "dependencies": { + "@cesium/wasm-splats": "^0.1.0-alpha.2", + "@spz-loader/core": "0.3.1", + "@tweenjs/tween.js": "^25.0.0", + "@zip.js/zip.js": "^2.8.1", + "autolinker": "^4.0.0", + "bitmap-sdf": "^1.0.3", + "dompurify": "^3.3.0", + "draco3d": "^1.5.1", + "earcut": "^3.0.0", + "grapheme-splitter": "^1.0.4", + "jsep": "^1.3.8", + "kdbush": "^4.0.1", + "ktx-parse": "^1.0.0", + "lerc": "^2.0.0", + "mersenne-twister": "^1.1.0", + "meshoptimizer": "^1.0.1", + "pako": "^2.0.4", + "protobufjs": "^8.0.0", + "rbush": "^4.0.1", + "topojson-client": "^3.1.0", + "urijs": "^1.19.7" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@cesium/wasm-splats": { + "version": "0.1.0-alpha.2", + "resolved": "https://npm.blendercg.art/@cesium/wasm-splats/-/wasm-splats-0.1.0-alpha.2.tgz", + "integrity": "sha512-t9pMkknv31hhIbLpMa8yPvmqfpvs5UkUjgqlQv9SeO8VerCXOYnyP8/486BDaFrztM0A7FMbRjsXtNeKvqQghA==", + "license": "Apache-2.0" + }, + "node_modules/@cesium/widgets": { + "version": "14.5.0", + "resolved": "https://npm.blendercg.art/@cesium/widgets/-/widgets-14.5.0.tgz", + "integrity": "sha512-h/hKVooXyOtUQJUtrfyBFGMIXb+Q3RLwqE6FzXfzyC0JQuuThLXCz8nRzCPbyiRuAR/aAYT/jbbhQrUxfiWqhQ==", + "license": "Apache-2.0", + "dependencies": { + "@cesium/engine": "^24.0.0", + "nosleep.js": "^0.12.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@jimp/core": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/core/-/core-1.6.1.tgz", + "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==", + "license": "MIT", + "dependencies": { + "@jimp/file-ops": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^21.3.3", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/diff/-/diff-1.6.1.tgz", + "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==", + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/file-ops/-/file-ops-1.6.1.tgz", + "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/js-bmp/-/js-bmp-1.6.1.tgz", + "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/js-gif/-/js-gif-1.6.1.tgz", + "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz", + "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/js-png/-/js-png-1.6.1.tgz", + "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/js-tiff/-/js-tiff-1.6.1.tgz", + "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz", + "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz", + "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/utils": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz", + "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-color/-/plugin-color-1.6.1.tgz", + "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz", + "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz", + "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz", + "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz", + "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz", + "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz", + "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz", + "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz", + "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz", + "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-print/-/plugin-print-1.6.1.tgz", + "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/types": "1.6.1", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz", + "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz", + "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz", + "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz", + "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/types/-/types-1.6.1.tgz", + "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/@jimp/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@spz-loader/core": { + "version": "0.3.1", + "resolved": "https://npm.blendercg.art/@spz-loader/core/-/core-0.3.1.tgz", + "integrity": "sha512-8qJ1WIBXaJu8HjnJAjYniE0kYcr0kCe5Hp7kDzYiGVvvd7zyrOBwbF5imoW5mvwx1Qba0hxGEK5R9jEoaHKJFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=16", + "pnpm": ">=8" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://npm.blendercg.art/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://npm.blendercg.art/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://npm.blendercg.art/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "3.0.4", + "resolved": "https://npm.blendercg.art/@types/mime/-/mime-3.0.4.tgz", + "integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://npm.blendercg.art/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://npm.blendercg.art/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@zip.js/zip.js": { + "version": "2.8.26", + "resolved": "https://npm.blendercg.art/@zip.js/zip.js/-/zip.js-2.8.26.tgz", + "integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==", + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=18.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://npm.blendercg.art/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://npm.blendercg.art/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://npm.blendercg.art/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://npm.blendercg.art/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/autolinker": { + "version": "4.1.5", + "resolved": "https://npm.blendercg.art/autolinker/-/autolinker-4.1.5.tgz", + "integrity": "sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "engines": { + "pnpm": ">=10.10.0" + } + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://npm.blendercg.art/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bitmap-sdf": { + "version": "1.0.4", + "resolved": "https://npm.blendercg.art/bitmap-sdf/-/bitmap-sdf-1.0.4.tgz", + "integrity": "sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==", + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://npm.blendercg.art/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://npm.blendercg.art/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "license": "MIT" + }, + "node_modules/cesium": { + "version": "1.140.0", + "resolved": "https://npm.blendercg.art/cesium/-/cesium-1.140.0.tgz", + "integrity": "sha512-3RvW0rvZWuXiS6regtNE5u9vt0uXohgpsRBIo6Qc922IIIamkitYiEdr4fg+u4qX4EoK9xS3BosCza7iPOExEQ==", + "license": "Apache-2.0", + "workspaces": [ + "packages/engine", + "packages/widgets", + "packages/sandcastle" + ], + "dependencies": { + "@cesium/engine": "^24.0.0", + "@cesium/widgets": "^14.5.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://npm.blendercg.art/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://npm.blendercg.art/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://npm.blendercg.art/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://npm.blendercg.art/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://npm.blendercg.art/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dompurify": { + "version": "3.4.1", + "resolved": "https://npm.blendercg.art/dompurify/-/dompurify-3.4.1.tgz", + "integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://npm.blendercg.art/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://npm.blendercg.art/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://npm.blendercg.art/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://npm.blendercg.art/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://npm.blendercg.art/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://npm.blendercg.art/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://npm.blendercg.art/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://npm.blendercg.art/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://npm.blendercg.art/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://npm.blendercg.art/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/gltf-pipeline": { + "version": "4.3.1", + "resolved": "https://npm.blendercg.art/gltf-pipeline/-/gltf-pipeline-4.3.1.tgz", + "integrity": "sha512-fqXOTYXmxGAxCqNZrybcGWr7hLMTqEDyM8cBQ/4cLBFPv1hTcqqn5kljJ61w2zoLj2T0ohFP5s3lwF/Il0oQxA==", + "license": "Apache-2.0", + "dependencies": { + "bluebird": "^3.7.2", + "cesium": "^1.131.0", + "draco3d": "^1.5.7", + "fs-extra": "^11.3.0", + "mime": "^3.0.0", + "object-hash": "^3.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "gltf-pipeline": "bin/gltf-pipeline.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://npm.blendercg.art/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://npm.blendercg.art/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://npm.blendercg.art/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://npm.blendercg.art/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://npm.blendercg.art/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://npm.blendercg.art/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://npm.blendercg.art/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jimp": { + "version": "1.6.1", + "resolved": "https://npm.blendercg.art/jimp/-/jimp-1.6.1.tgz", + "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/diff": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-gif": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-blur": "1.6.1", + "@jimp/plugin-circle": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-contain": "1.6.1", + "@jimp/plugin-cover": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-displace": "1.6.1", + "@jimp/plugin-dither": "1.6.1", + "@jimp/plugin-fisheye": "1.6.1", + "@jimp/plugin-flip": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/plugin-mask": "1.6.1", + "@jimp/plugin-print": "1.6.1", + "@jimp/plugin-quantize": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/plugin-rotate": "1.6.1", + "@jimp/plugin-threshold": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://npm.blendercg.art/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://npm.blendercg.art/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://npm.blendercg.art/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://npm.blendercg.art/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, + "node_modules/ktx-parse": { + "version": "1.1.0", + "resolved": "https://npm.blendercg.art/ktx-parse/-/ktx-parse-1.1.0.tgz", + "integrity": "sha512-mKp3y+FaYgR7mXWAbyyzpa/r1zDWeaunH+INJO4fou3hb45XuNSwar+7llrRyvpMWafxSIi99RNFJ05MHedaJQ==", + "license": "MIT" + }, + "node_modules/lerc": { + "version": "2.0.0", + "resolved": "https://npm.blendercg.art/lerc/-/lerc-2.0.0.tgz", + "integrity": "sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==", + "license": "Apache-2.0" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://npm.blendercg.art/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/mersenne-twister": { + "version": "1.1.0", + "resolved": "https://npm.blendercg.art/mersenne-twister/-/mersenne-twister-1.1.0.tgz", + "integrity": "sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==", + "license": "MIT" + }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "resolved": "https://npm.blendercg.art/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://npm.blendercg.art/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://npm.blendercg.art/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nosleep.js": { + "version": "0.12.0", + "resolved": "https://npm.blendercg.art/nosleep.js/-/nosleep.js-0.12.0.tgz", + "integrity": "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==", + "license": "MIT" + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://npm.blendercg.art/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://npm.blendercg.art/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://npm.blendercg.art/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://npm.blendercg.art/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://npm.blendercg.art/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://npm.blendercg.art/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://npm.blendercg.art/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://npm.blendercg.art/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://npm.blendercg.art/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/protobufjs": { + "version": "8.0.3", + "resolved": "https://npm.blendercg.art/protobufjs/-/protobufjs-8.0.3.tgz", + "integrity": "sha512-LBYnMWkKLB8fE/ljROPDbCl7mgLSlI+oBe1fAAr5MTqFg4TIi0tYrVVurJvQggOjnUYMQtEZBjrej59ojMNTHQ==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://npm.blendercg.art/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/rbush": { + "version": "4.0.1", + "resolved": "https://npm.blendercg.art/rbush/-/rbush-4.0.1.tgz", + "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", + "license": "MIT", + "dependencies": { + "quickselect": "^3.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://npm.blendercg.art/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://npm.blendercg.art/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/seinjs-texture-compressor": { + "version": "1.0.3", + "resolved": "https://npm.blendercg.art/seinjs-texture-compressor/-/seinjs-texture-compressor-1.0.3.tgz", + "integrity": "sha512-qPgi/aRkTjydc/e9dOezZJAAcC/5FCQTmyfqsUvFEDCnYu5OmLnLgsort/L6ldqiHO+P0tSJVGzoxI9LwNqJIA==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.10", + "image-size": "^0.7.4", + "systeminformation": "^4.16.0" + }, + "bin": { + "seinjs-texture-compressor": "bin/texture-compressor.js" + } + }, + "node_modules/seinjs-texture-compressor/node_modules/image-size": { + "version": "0.7.5", + "resolved": "https://npm.blendercg.art/image-size/-/image-size-0.7.5.tgz", + "integrity": "sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==", + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/simple-xml-to-json": { + "version": "1.2.7", + "resolved": "https://npm.blendercg.art/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", + "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://npm.blendercg.art/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://npm.blendercg.art/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://npm.blendercg.art/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://npm.blendercg.art/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/systeminformation": { + "version": "4.34.23", + "resolved": "https://npm.blendercg.art/systeminformation/-/systeminformation-4.34.23.tgz", + "integrity": "sha512-33+lQwlLxXoxy0o9WLOgw8OjbXeS3Jv+pSl+nxKc2AOClBI28HsdRPpH0u9Xa9OVjHLT9vonnOMw1ug7YXI0dA==", + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=4.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://npm.blendercg.art/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://npm.blendercg.art/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://npm.blendercg.art/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://npm.blendercg.art/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://npm.blendercg.art/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://npm.blendercg.art/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://npm.blendercg.art/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://npm.blendercg.art/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://npm.blendercg.art/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "license": "MIT" + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://npm.blendercg.art/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/utif2/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://npm.blendercg.art/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://npm.blendercg.art/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://npm.blendercg.art/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://npm.blendercg.art/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://npm.blendercg.art/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://npm.blendercg.art/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://npm.blendercg.art/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://npm.blendercg.art/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://npm.blendercg.art/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ff3cfbe --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "@g1sir8b/gltf-model-compressor", + "version": "1.0.1", + "description": "Standalone npm package for GLTF/GLB optimization and texture compression.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "README.md" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc -p tsconfig.json" + }, + "dependencies": { + "gl-matrix": "^3.4.3", + "gltf-pipeline": "^4.1.0", + "image-size": "^2.0.2", + "jimp": "^1.6.1", + "mime": "^3.0.0", + "seinjs-texture-compressor": "^1.0.3" + }, + "devDependencies": { + "@types/mime": "^3.0.0", + "@types/node": "^20.0.0", + "typescript": "^5.2.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..665fbde --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1549 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + gl-matrix: + specifier: ^3.4.3 + version: 3.4.4 + gltf-pipeline: + specifier: ^4.1.0 + version: 4.3.1 + image-size: + specifier: ^2.0.2 + version: 2.0.2 + jimp: + specifier: ^1.6.1 + version: 1.6.1 + mime: + specifier: ^3.0.0 + version: 3.0.0 + seinjs-texture-compressor: + specifier: ^1.0.3 + version: 1.0.3 + sharp: + specifier: ^0.32.1 + version: 0.32.6 + devDependencies: + '@types/mime': + specifier: ^3.0.0 + version: 3.0.4 + '@types/node': + specifier: ^20.0.0 + version: 20.19.39 + typescript: + specifier: ^5.2.2 + version: 5.9.3 + +packages: + + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + + '@cesium/engine@24.0.0': + resolution: {integrity: sha512-zJ2gl0tyw/FFhBtvp6UYw+0JQJb2J9EiTJYvVSndc6+6qPR5GHLFzFjA1msLLxucfcpc7uI9R2pXNEPluheR/g==} + engines: {node: '>=20.19.0'} + + '@cesium/wasm-splats@0.1.0-alpha.2': + resolution: {integrity: sha512-t9pMkknv31hhIbLpMa8yPvmqfpvs5UkUjgqlQv9SeO8VerCXOYnyP8/486BDaFrztM0A7FMbRjsXtNeKvqQghA==} + + '@cesium/widgets@14.5.0': + resolution: {integrity: sha512-h/hKVooXyOtUQJUtrfyBFGMIXb+Q3RLwqE6FzXfzyC0JQuuThLXCz8nRzCPbyiRuAR/aAYT/jbbhQrUxfiWqhQ==} + engines: {node: '>=20.19.0'} + + '@jimp/core@1.6.1': + resolution: {integrity: sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==} + engines: {node: '>=18'} + + '@jimp/diff@1.6.1': + resolution: {integrity: sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==} + engines: {node: '>=18'} + + '@jimp/file-ops@1.6.1': + resolution: {integrity: sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==} + engines: {node: '>=18'} + + '@jimp/js-bmp@1.6.1': + resolution: {integrity: sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==} + engines: {node: '>=18'} + + '@jimp/js-gif@1.6.1': + resolution: {integrity: sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==} + engines: {node: '>=18'} + + '@jimp/js-jpeg@1.6.1': + resolution: {integrity: sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==} + engines: {node: '>=18'} + + '@jimp/js-png@1.6.1': + resolution: {integrity: sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==} + engines: {node: '>=18'} + + '@jimp/js-tiff@1.6.1': + resolution: {integrity: sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==} + engines: {node: '>=18'} + + '@jimp/plugin-blit@1.6.1': + resolution: {integrity: sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==} + engines: {node: '>=18'} + + '@jimp/plugin-blur@1.6.1': + resolution: {integrity: sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==} + engines: {node: '>=18'} + + '@jimp/plugin-circle@1.6.1': + resolution: {integrity: sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==} + engines: {node: '>=18'} + + '@jimp/plugin-color@1.6.1': + resolution: {integrity: sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==} + engines: {node: '>=18'} + + '@jimp/plugin-contain@1.6.1': + resolution: {integrity: sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==} + engines: {node: '>=18'} + + '@jimp/plugin-cover@1.6.1': + resolution: {integrity: sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==} + engines: {node: '>=18'} + + '@jimp/plugin-crop@1.6.1': + resolution: {integrity: sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==} + engines: {node: '>=18'} + + '@jimp/plugin-displace@1.6.1': + resolution: {integrity: sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==} + engines: {node: '>=18'} + + '@jimp/plugin-dither@1.6.1': + resolution: {integrity: sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==} + engines: {node: '>=18'} + + '@jimp/plugin-fisheye@1.6.1': + resolution: {integrity: sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==} + engines: {node: '>=18'} + + '@jimp/plugin-flip@1.6.1': + resolution: {integrity: sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==} + engines: {node: '>=18'} + + '@jimp/plugin-hash@1.6.1': + resolution: {integrity: sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==} + engines: {node: '>=18'} + + '@jimp/plugin-mask@1.6.1': + resolution: {integrity: sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==} + engines: {node: '>=18'} + + '@jimp/plugin-print@1.6.1': + resolution: {integrity: sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==} + engines: {node: '>=18'} + + '@jimp/plugin-quantize@1.6.1': + resolution: {integrity: sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==} + engines: {node: '>=18'} + + '@jimp/plugin-resize@1.6.1': + resolution: {integrity: sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==} + engines: {node: '>=18'} + + '@jimp/plugin-rotate@1.6.1': + resolution: {integrity: sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==} + engines: {node: '>=18'} + + '@jimp/plugin-threshold@1.6.1': + resolution: {integrity: sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==} + engines: {node: '>=18'} + + '@jimp/types@1.6.1': + resolution: {integrity: sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==} + engines: {node: '>=18'} + + '@jimp/utils@1.6.1': + resolution: {integrity: sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==} + engines: {node: '>=18'} + + '@spz-loader/core@0.3.1': + resolution: {integrity: sha512-8qJ1WIBXaJu8HjnJAjYniE0kYcr0kCe5Hp7kDzYiGVvvd7zyrOBwbF5imoW5mvwx1Qba0hxGEK5R9jEoaHKJFA==} + engines: {node: '>=16', pnpm: '>=8'} + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tweenjs/tween.js@25.0.0': + resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==} + + '@types/mime@3.0.4': + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + + '@types/node@16.9.1': + resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} + + '@types/node@20.19.39': + resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@zip.js/zip.js@2.8.26': + resolution: {integrity: sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + any-base@1.1.0: + resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + autolinker@4.1.5: + resolution: {integrity: sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==} + engines: {pnpm: '>=10.10.0'} + + await-to-js@3.0.0: + resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} + engines: {node: '>=6.0.0'} + + b4a@1.8.0: + resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.7.1: + resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.9.0: + resolution: {integrity: sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.2: + resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bitmap-sdf@1.0.4: + resolution: {integrity: sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + bmp-ts@1.0.9: + resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + cesium@1.140.0: + resolution: {integrity: sha512-3RvW0rvZWuXiS6regtNE5u9vt0uXohgpsRBIo6Qc922IIIamkitYiEdr4fg+u4qX4EoK9xS3BosCza7iPOExEQ==} + engines: {node: '>=20.19.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dompurify@3.4.1: + resolution: {integrity: sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==} + + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + + earcut@3.0.2: + resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + exif-parser@0.1.12: + resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + gifwrap@0.10.1: + resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + + gltf-pipeline@4.3.1: + resolution: {integrity: sha512-fqXOTYXmxGAxCqNZrybcGWr7hLMTqEDyM8cBQ/4cLBFPv1hTcqqn5kljJ61w2zoLj2T0ohFP5s3lwF/Il0oQxA==} + engines: {node: '>=16.0.0'} + hasBin: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + image-q@4.0.0: + resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + + image-size@0.7.5: + resolution: {integrity: sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==} + engines: {node: '>=6.9.0'} + hasBin: true + + image-size@2.0.2: + resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} + engines: {node: '>=16.x'} + hasBin: true + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + jimp@1.6.1: + resolution: {integrity: sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==} + engines: {node: '>=18'} + + jpeg-js@0.4.4: + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + kdbush@4.0.2: + resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + + ktx-parse@1.1.0: + resolution: {integrity: sha512-mKp3y+FaYgR7mXWAbyyzpa/r1zDWeaunH+INJO4fou3hb45XuNSwar+7llrRyvpMWafxSIi99RNFJ05MHedaJQ==} + + lerc@2.0.0: + resolution: {integrity: sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + mersenne-twister@1.1.0: + resolution: {integrity: sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==} + + meshoptimizer@1.1.1: + resolution: {integrity: sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.89.0: + resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==} + engines: {node: '>=10'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + nosleep.js@0.12.0: + resolution: {integrity: sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + omggif@1.0.10: + resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + + parse-bmfont-ascii@1.0.6: + resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} + + parse-bmfont-binary@1.0.6: + resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} + + parse-bmfont-xml@1.1.6: + resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + + pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + + pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + + protobufjs@8.0.3: + resolution: {integrity: sha512-LBYnMWkKLB8fE/ljROPDbCl7mgLSlI+oBe1fAAr5MTqFg4TIi0tYrVVurJvQggOjnUYMQtEZBjrej59ojMNTHQ==} + engines: {node: '>=12.0.0'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + + rbush@4.0.1: + resolution: {integrity: sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + seinjs-texture-compressor@1.0.3: + resolution: {integrity: sha512-qPgi/aRkTjydc/e9dOezZJAAcC/5FCQTmyfqsUvFEDCnYu5OmLnLgsort/L6ldqiHO+P0tSJVGzoxI9LwNqJIA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.32.6: + resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} + engines: {node: '>=14.15.0'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + simple-xml-to-json@1.2.7: + resolution: {integrity: sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==} + engines: {node: '>=20.12.2'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + streamx@2.25.0: + resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + + systeminformation@4.34.23: + resolution: {integrity: sha512-33+lQwlLxXoxy0o9WLOgw8OjbXeS3Jv+pSl+nxKc2AOClBI28HsdRPpH0u9Xa9OVjHLT9vonnOMw1ug7YXI0dA==} + engines: {node: '>=4.0.0'} + os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos] + hasBin: true + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-fs@3.1.2: + resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + topojson-client@3.1.0: + resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} + hasBin: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + + utif2@4.1.0: + resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-parse-from-string@1.0.1: + resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@borewit/text-codec@0.2.2': {} + + '@cesium/engine@24.0.0': + dependencies: + '@cesium/wasm-splats': 0.1.0-alpha.2 + '@spz-loader/core': 0.3.1 + '@tweenjs/tween.js': 25.0.0 + '@zip.js/zip.js': 2.8.26 + autolinker: 4.1.5 + bitmap-sdf: 1.0.4 + dompurify: 3.4.1 + draco3d: 1.5.7 + earcut: 3.0.2 + grapheme-splitter: 1.0.4 + jsep: 1.4.0 + kdbush: 4.0.2 + ktx-parse: 1.1.0 + lerc: 2.0.0 + mersenne-twister: 1.1.0 + meshoptimizer: 1.1.1 + pako: 2.1.0 + protobufjs: 8.0.3 + rbush: 4.0.1 + topojson-client: 3.1.0 + urijs: 1.19.11 + + '@cesium/wasm-splats@0.1.0-alpha.2': {} + + '@cesium/widgets@14.5.0': + dependencies: + '@cesium/engine': 24.0.0 + nosleep.js: 0.12.0 + + '@jimp/core@1.6.1': + dependencies: + '@jimp/file-ops': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + await-to-js: 3.0.0 + exif-parser: 0.1.12 + file-type: 21.3.4 + mime: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@jimp/diff@1.6.1': + dependencies: + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + pixelmatch: 5.3.0 + transitivePeerDependencies: + - supports-color + + '@jimp/file-ops@1.6.1': {} + + '@jimp/js-bmp@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + bmp-ts: 1.0.9 + transitivePeerDependencies: + - supports-color + + '@jimp/js-gif@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + gifwrap: 0.10.1 + omggif: 1.0.10 + transitivePeerDependencies: + - supports-color + + '@jimp/js-jpeg@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + jpeg-js: 0.4.4 + transitivePeerDependencies: + - supports-color + + '@jimp/js-png@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + pngjs: 7.0.0 + transitivePeerDependencies: + - supports-color + + '@jimp/js-tiff@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + utif2: 4.1.0 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-blit@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-blur@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/utils': 1.6.1 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-circle@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-color@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + tinycolor2: 1.6.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-contain@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-blit': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-cover@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-crop': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-crop@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-displace@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-dither@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + + '@jimp/plugin-fisheye@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-flip@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-hash@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/js-bmp': 1.6.1 + '@jimp/js-jpeg': 1.6.1 + '@jimp/js-png': 1.6.1 + '@jimp/js-tiff': 1.6.1 + '@jimp/plugin-color': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + any-base: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-mask@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + zod: 3.25.76 + + '@jimp/plugin-print@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/js-jpeg': 1.6.1 + '@jimp/js-png': 1.6.1 + '@jimp/plugin-blit': 1.6.1 + '@jimp/types': 1.6.1 + parse-bmfont-ascii: 1.0.6 + parse-bmfont-binary: 1.0.6 + parse-bmfont-xml: 1.1.6 + simple-xml-to-json: 1.2.7 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-quantize@1.6.1': + dependencies: + image-q: 4.0.0 + zod: 3.25.76 + + '@jimp/plugin-resize@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/types': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-rotate@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-crop': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/plugin-threshold@1.6.1': + dependencies: + '@jimp/core': 1.6.1 + '@jimp/plugin-color': 1.6.1 + '@jimp/plugin-hash': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@jimp/types@1.6.1': + dependencies: + zod: 3.25.76 + + '@jimp/utils@1.6.1': + dependencies: + '@jimp/types': 1.6.1 + tinycolor2: 1.6.0 + + '@spz-loader/core@0.3.1': {} + + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tweenjs/tween.js@25.0.0': {} + + '@types/mime@3.0.4': {} + + '@types/node@16.9.1': {} + + '@types/node@20.19.39': + dependencies: + undici-types: 6.21.0 + + '@types/trusted-types@2.0.7': + optional: true + + '@zip.js/zip.js@2.8.26': {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + any-base@1.1.0: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + autolinker@4.1.5: + dependencies: + tslib: 2.8.1 + + await-to-js@3.0.0: {} + + b4a@1.8.0: {} + + bare-events@2.8.2: {} + + bare-fs@4.7.1: + dependencies: + bare-events: 2.8.2 + bare-path: 3.0.0 + bare-stream: 2.13.1(bare-events@2.8.2) + bare-url: 2.4.2 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.9.0: {} + + bare-path@3.0.0: + dependencies: + bare-os: 3.9.0 + + bare-stream@2.13.1(bare-events@2.8.2): + dependencies: + streamx: 2.25.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.2: + dependencies: + bare-path: 3.0.0 + + base64-js@1.5.1: {} + + bitmap-sdf@1.0.4: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird@3.7.2: {} + + bmp-ts@1.0.9: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + cesium@1.140.0: + dependencies: + '@cesium/engine': 24.0.0 + '@cesium/widgets': 14.5.0 + + chownr@1.1.4: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + commander@2.20.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + detect-libc@2.1.2: {} + + dompurify@3.4.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + draco3d@1.5.7: {} + + earcut@3.0.2: {} + + emoji-regex@8.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + escalade@3.2.0: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + exif-parser@0.1.12: {} + + expand-template@2.0.3: {} + + fast-fifo@1.3.2: {} + + file-type@21.3.4: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + fs-constants@1.0.0: {} + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + get-caller-file@2.0.5: {} + + gifwrap@0.10.1: + dependencies: + image-q: 4.0.0 + omggif: 1.0.10 + + github-from-package@0.0.0: {} + + gl-matrix@3.4.4: {} + + gltf-pipeline@4.3.1: + dependencies: + bluebird: 3.7.2 + cesium: 1.140.0 + draco3d: 1.5.7 + fs-extra: 11.3.4 + mime: 3.0.0 + object-hash: 3.0.0 + yargs: 17.7.2 + + graceful-fs@4.2.11: {} + + grapheme-splitter@1.0.4: {} + + ieee754@1.2.1: {} + + image-q@4.0.0: + dependencies: + '@types/node': 16.9.1 + + image-size@0.7.5: {} + + image-size@2.0.2: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-arrayish@0.3.4: {} + + is-fullwidth-code-point@3.0.0: {} + + jimp@1.6.1: + dependencies: + '@jimp/core': 1.6.1 + '@jimp/diff': 1.6.1 + '@jimp/js-bmp': 1.6.1 + '@jimp/js-gif': 1.6.1 + '@jimp/js-jpeg': 1.6.1 + '@jimp/js-png': 1.6.1 + '@jimp/js-tiff': 1.6.1 + '@jimp/plugin-blit': 1.6.1 + '@jimp/plugin-blur': 1.6.1 + '@jimp/plugin-circle': 1.6.1 + '@jimp/plugin-color': 1.6.1 + '@jimp/plugin-contain': 1.6.1 + '@jimp/plugin-cover': 1.6.1 + '@jimp/plugin-crop': 1.6.1 + '@jimp/plugin-displace': 1.6.1 + '@jimp/plugin-dither': 1.6.1 + '@jimp/plugin-fisheye': 1.6.1 + '@jimp/plugin-flip': 1.6.1 + '@jimp/plugin-hash': 1.6.1 + '@jimp/plugin-mask': 1.6.1 + '@jimp/plugin-print': 1.6.1 + '@jimp/plugin-quantize': 1.6.1 + '@jimp/plugin-resize': 1.6.1 + '@jimp/plugin-rotate': 1.6.1 + '@jimp/plugin-threshold': 1.6.1 + '@jimp/types': 1.6.1 + '@jimp/utils': 1.6.1 + transitivePeerDependencies: + - supports-color + + jpeg-js@0.4.4: {} + + jsep@1.4.0: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + kdbush@4.0.2: {} + + ktx-parse@1.1.0: {} + + lerc@2.0.0: {} + + long@5.3.2: {} + + mersenne-twister@1.1.0: {} + + meshoptimizer@1.1.1: {} + + mime@3.0.0: {} + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + ms@2.1.3: {} + + napi-build-utils@2.0.0: {} + + node-abi@3.89.0: + dependencies: + semver: 7.7.4 + + node-addon-api@6.1.0: {} + + nosleep.js@0.12.0: {} + + object-hash@3.0.0: {} + + omggif@1.0.10: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pako@1.0.11: {} + + pako@2.1.0: {} + + parse-bmfont-ascii@1.0.6: {} + + parse-bmfont-binary@1.0.6: {} + + parse-bmfont-xml@1.1.6: + dependencies: + xml-parse-from-string: 1.0.1 + xml2js: 0.5.0 + + pixelmatch@5.3.0: + dependencies: + pngjs: 6.0.0 + + pngjs@6.0.0: {} + + pngjs@7.0.0: {} + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.89.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + protobufjs@8.0.3: + dependencies: + '@types/node': 20.19.39 + long: 5.3.2 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + quickselect@3.0.0: {} + + rbush@4.0.1: + dependencies: + quickselect: 3.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + require-directory@2.1.1: {} + + safe-buffer@5.2.1: {} + + sax@1.6.0: {} + + seinjs-texture-compressor@1.0.3: + dependencies: + argparse: 1.0.10 + image-size: 0.7.5 + systeminformation: 4.34.23 + + semver@7.7.4: {} + + sharp@0.32.6: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + node-addon-api: 6.1.0 + prebuild-install: 7.1.3 + semver: 7.7.4 + simple-get: 4.0.1 + tar-fs: 3.1.2 + tunnel-agent: 0.6.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + simple-xml-to-json@1.2.7: {} + + sprintf-js@1.0.3: {} + + streamx@2.25.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@2.0.1: {} + + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + + systeminformation@4.34.23: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-fs@3.1.2: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.0 + optionalDependencies: + bare-fs: 4.7.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.2.0: + dependencies: + b4a: 1.8.0 + bare-fs: 4.7.1 + fast-fifo: 1.3.2 + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.0 + transitivePeerDependencies: + - react-native-b4a + + tinycolor2@1.6.0: {} + + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + topojson-client@3.1.0: + dependencies: + commander: 2.20.3 + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + typescript@5.9.3: {} + + uint8array-extras@1.5.0: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + urijs@1.19.11: {} + + utif2@4.1.0: + dependencies: + pako: 1.0.11 + + util-deprecate@1.0.2: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + xml-parse-from-string@1.0.1: {} + + xml2js@0.5.0: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..c7e719e --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +onlyBuiltDependencies: + - '@webassembly/sharp' + - protobufjs +supportedArchitectures: + os: [win32] + cpu: [x64] diff --git a/src/compressTexture.ts b/src/compressTexture.ts new file mode 100644 index 0000000..aa550a5 --- /dev/null +++ b/src/compressTexture.ts @@ -0,0 +1,286 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { spawn } from 'child_process'; +import { readImageMetadata } from './imageTools'; +import { showInfo } from './utils'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { getBinaryDirectory } = require('seinjs-texture-compressor/dist/cli/lib/utilities'); + +export type TextureCompressionQuality = 'high' | 'medium' | 'low'; + +function resolveTmpDir(): string { + const explicit = (process.env.XR_TEX_TMP_DIR || '').trim(); + if (explicit) { + return path.resolve(explicit); + } + + const preferRam = process.env.XR_TMP_PREFER_RAM !== '0'; + if (preferRam && process.platform !== 'win32') { + const shmBase = '/dev/shm'; + try { + if (fs.existsSync(shmBase) && fs.statSync(shmBase).isDirectory()) { + return path.join(shmBase, 'xr-frame-toolkit-tmp'); + } + } catch { + // Ignore tmp dir probing errors. + } + } + + return path.resolve(os.tmpdir(), 'xr-frame-toolkit-tmp'); +} + +const TMP_DIR = resolveTmpDir(); +if (!fs.existsSync(TMP_DIR)) { + fs.mkdirSync(TMP_DIR, { recursive: true }); +} + +showInfo(`[tex-tmp] using tmp dir: ${TMP_DIR}`); + +const HEARTBEAT_MS = Math.max(5_000, Number(process.env.XR_TEX_HEARTBEAT_MS || 30_000)); +const BASE_STEP_TIMEOUT_MS = Math.max(30_000, Number(process.env.XR_TEX_STEP_TIMEOUT_MS || 20 * 60 * 1000)); +const ETCSLOW_STEP_TIMEOUT_MS = Math.max(60_000, Number(process.env.XR_TEX_ETCSLOW_TIMEOUT_MS || 120 * 60 * 1000)); + +function mipLevels(width: number): number { + return Math.floor(Math.log2(Math.max(1, width))) + 1; +} + +async function runToolWithWatch( + src: string, + destFormat: 'astc' | 'etc' | 'pvrtc' | 's3tc', + destEncoding: string, + destQuality: string, + inputPath: string, + outputPath: string, + widthForMips: number +) { + const binDir = await getBinaryDirectory(); + const isS3TC = destFormat === 's3tc'; + const toolName = isS3TC ? 'crunch' : 'PVRTexToolCLI'; + const toolPath = path.join(binDir, toolName); + + const flags = isS3TC + ? [ + '-file', + inputPath, + '-out', + outputPath, + '-fileformat', + 'ktx', + `-${destEncoding}`, + '-dxtQuality', + `${destQuality}`, + '-helperThreads', + os.cpus().length.toString(), + '-mipMode', + 'Generate', + '-maxmips', + `${mipLevels(widthForMips)}` + ] + : [ + '-i', + inputPath, + '-o', + outputPath, + '-f', + `${destEncoding}`, + '-q', + `${destQuality}`, + '-pot', + '+', + '-m', + `${mipLevels(widthForMips)}` + ]; + + if (!isS3TC && destFormat === 'pvrtc') { + flags.push('-square', '+'); + } + + showInfo(`Using flags: ${flags.join(',')}`); + + const startedAt = Date.now(); + const timeoutMs = destFormat === 'etc' && destQuality === 'etcslow' + ? ETCSLOW_STEP_TIMEOUT_MS + : BASE_STEP_TIMEOUT_MS; + + await new Promise((resolve, reject) => { + const child = spawn(toolPath, flags, { + env: { + ...process.env, + PATH: `${binDir}${path.delimiter}${process.env.PATH || ''}` + }, + windowsHide: true + }); + + let timedOut = false; + const heartbeatTimer = setInterval(() => { + const elapsed = Math.round((Date.now() - startedAt) / 1000); + showInfo(`[tex-watch] running ${src} -> ${destFormat} (${destEncoding}/${destQuality}), ${elapsed}s elapsed`); + }, HEARTBEAT_MS); + + const timeoutTimer = setTimeout(() => { + timedOut = true; + const elapsed = Math.round((Date.now() - startedAt) / 1000); + showInfo(`[tex-watch] timeout ${src} -> ${destFormat} after ${elapsed}s, killing pid=${child.pid || -1}`); + + try { + child.kill('SIGKILL'); + } catch { + // Ignore kill errors. + } + + if (process.platform === 'win32' && child.pid) { + try { + const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], { + stdio: 'ignore', + windowsHide: true + }); + killer.unref(); + } catch { + // Ignore taskkill errors. + } + } + }, timeoutMs); + + child.stdout?.on('data', (data) => { + const text = String(data || '').trim(); + if (text) { + showInfo(`[tex-cli][${destFormat}] ${text}`); + } + }); + + child.stderr?.on('data', (data) => { + const text = String(data || '').trim(); + if (text) { + showInfo(`[tex-cli][${destFormat}] ${text}`); + } + }); + + child.once('error', (error) => { + clearInterval(heartbeatTimer); + clearTimeout(timeoutTimer); + reject(error); + }); + + child.once('exit', (code) => { + clearInterval(heartbeatTimer); + clearTimeout(timeoutTimer); + + if (timedOut) { + reject( + new Error( + `Compression timeout: ${src} -> ${destFormat}, quality=${destQuality}, limit=${Math.round(timeoutMs / 1000)}s` + ) + ); + return; + } + + if (code !== 0) { + reject(new Error(`Compression tool exited with error code ${code}`)); + return; + } + + resolve(); + }); + }); +} + +export default async function compressTexture( + src: string, + buffer: Buffer, + quality: TextureCompressionQuality, + isNormalMap: boolean +): Promise<{ astc: Buffer; pvrtc: Buffer; s3tc: Buffer; etc: Buffer }> { + const isTransparent = /(png|exr)$/i.test(src); + const metadata = readImageMetadata(buffer); + const widthForMips = metadata?.width || 1; + + if (!metadata?.width) { + showInfo(`[tex-debug][mips] ${src}: width unknown, fallback width=1`); + } + + let destEncoding = ''; + let destQuality = ''; + const runId = `${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; + const tmpSrcPath = path.resolve(TMP_DIR, `compress_tex_${runId}_src${path.extname(src)}`); + fs.writeFileSync(tmpSrcPath, buffer); + + const tmpDestPaths: string[] = []; + const result = {} as { astc: Buffer; pvrtc: Buffer; s3tc: Buffer; etc: Buffer }; + + try { + for (const destFormat of ['astc', 'etc', 'pvrtc', 's3tc'] as const) { + if (destFormat === 'astc') { + if (quality === 'high') { + destEncoding = (isTransparent || isNormalMap) ? 'ASTC_4x4' : 'ASTC_5x5'; + destQuality = 'astcthorough'; + } else if (quality === 'medium') { + destEncoding = (isTransparent || isNormalMap) ? 'ASTC_5x5' : 'ASTC_6x6'; + destQuality = 'astcmedium'; + } else { + destEncoding = (isTransparent || isNormalMap) ? 'ASTC_6x6' : 'ASTC_8x6'; + destQuality = 'astcfast'; + } + } else if (destFormat === 'pvrtc') { + if (quality === 'low') { + destEncoding = (isTransparent && !isNormalMap) ? 'PVRTC1_2' : 'PVRTC1_2_RGB'; + destQuality = 'pvrtcbest'; + } else { + destEncoding = (isTransparent && !isNormalMap) ? 'PVRTC1_4' : 'PVRTC1_4_RGB'; + destQuality = 'pvrtcnormal'; + } + } else if (destFormat === 'etc') { + destEncoding = (isTransparent && !isNormalMap) ? 'ETC2_RGBA' : 'ETC2_RGB'; + destQuality = 'etcfast'; + } else if (destFormat === 's3tc') { + if (quality === 'low') { + destEncoding = (isTransparent && !isNormalMap) ? 'DXT3' : 'DXT1'; + destQuality = 'better'; + } else { + destEncoding = (isTransparent && !isNormalMap) ? 'DXT5' : 'DXT3'; + destQuality = 'fast'; + } + } + + showInfo(`packing: ${src} to ${destFormat}`); + const tmpDestPath = path.resolve(TMP_DIR, `compress_tex_${runId}_${destFormat}.ktx`); + tmpDestPaths.push(tmpDestPath); + + try { + await runToolWithWatch( + src, + destFormat, + destEncoding, + destQuality, + tmpSrcPath, + tmpDestPath, + widthForMips + ); + result[destFormat] = fs.readFileSync(tmpDestPath); + } catch (error: any) { + throw new Error(`Compress error: '${error.message}'`); + } + } + } finally { + try { + if (fs.existsSync(tmpSrcPath)) { + fs.unlinkSync(tmpSrcPath); + } + } catch { + // Ignore cleanup errors. + } + + for (const tmpDestPath of tmpDestPaths) { + try { + if (fs.existsSync(tmpDestPath)) { + fs.unlinkSync(tmpDestPath); + } + } catch { + // Ignore cleanup errors. + } + } + } + + return result; +} diff --git a/src/imageTools.ts b/src/imageTools.ts new file mode 100644 index 0000000..f1c470c --- /dev/null +++ b/src/imageTools.ts @@ -0,0 +1,46 @@ +import * as path from 'path'; +import { imageSize } from 'image-size'; +import { Jimp, JimpMime } from 'jimp'; + +export interface ImageMetadata { + width?: number; + height?: number; + type?: string; +} + +const MIME_BY_EXT: Record = { + '.bmp': JimpMime.bmp, + '.gif': JimpMime.gif, + '.jpeg': JimpMime.jpeg, + '.jpg': JimpMime.jpeg, + '.png': JimpMime.png, + '.tif': JimpMime.tiff, + '.tiff': JimpMime.tiff +}; + +export function readImageMetadata(buffer: Buffer): ImageMetadata { + const result = imageSize(buffer); + return { + width: result.width, + height: result.height, + type: result.type + }; +} + +export async function resizeImageBuffer( + buffer: Buffer, + relativePath: string, + width: number, + height: number +): Promise { + const ext = path.extname(relativePath).toLowerCase(); + const mime = MIME_BY_EXT[ext]; + + if (!mime) { + return null; + } + + const image = await Jimp.read(buffer); + image.resize({ w: width, h: height }); + return image.getBuffer(mime as any); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ee30cb3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +export { default, optimizeModel, optimizeModelWithOptions } from './modelCompressor'; +export { optimizeModel as compressModel } from './modelCompressor'; +export type { CompressTex, OptimizeModelOptions } from './modelCompressor'; +export { default as compressTexture } from './compressTexture'; diff --git a/src/modelCompressor.ts b/src/modelCompressor.ts new file mode 100644 index 0000000..4ffc14a --- /dev/null +++ b/src/modelCompressor.ts @@ -0,0 +1,1015 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as gltfPipe from 'gltf-pipeline'; +import * as mime from 'mime'; +import { vec2, vec3 } from 'gl-matrix'; +import compressTexture from './compressTexture'; +import { readImageMetadata, resizeImageBuffer } from './imageTools'; +import { + isGLTFFile, + readFileBuffer, + readFileJson, + showInfo, + showWarn, + writeFile +} from './utils'; + +export type CompressTex = 'high' | 'medium' | 'low' | 'off'; + +export interface OptimizeModelOptions { + input: string; + output: string; + compressTex?: CompressTex; + toGLB?: boolean; +} + +interface ModelEntity { + iDir: string; + file: string; + isGLB: boolean; + oDir: string; + toGLB: boolean; + compressTex: CompressTex; +} + +function getTypeLength(type: string) { + switch (type) { + case 'SCALAR': + return 1; + case 'VEC2': + return 2; + case 'VEC3': + return 3; + case 'VEC4': + case 'MAT2': + return 4; + case 'MAT3': + return 9; + case 'MAT4': + return 16; + default: + return 0; + } +} + +function getComponentByteLengthAndClass(type: number): [number, new (...args: any[]) => ArrayBufferView] { + switch (type) { + case 5120: + return [1, Int8Array]; + case 5121: + return [1, Uint8Array]; + case 5122: + return [2, Int16Array]; + case 5123: + return [2, Uint16Array]; + case 5125: + return [4, Uint32Array]; + case 5126: + return [4, Float32Array]; + default: + return [4, Float32Array]; + } +} + +function getEntity(filePath: string, outputDir: string): ModelEntity { + const iDir = path.dirname(filePath); + const file = path.basename(filePath); + const isGLB = path.extname(filePath).toLowerCase() === '.glb'; + const inputFolderName = path.basename(path.dirname(filePath)); + const oDir = path.resolve(outputDir, isGLB ? file.replace(/\.glb$/i, '') : inputFolderName); + + return { + iDir, + file, + isGLB, + oDir, + toGLB: false, + compressTex: 'off' + }; +} + +async function processGlTF(entity: ModelEntity): Promise<{ + gltf: any; + buffer: Buffer; + assets: Record; + skip?: boolean; +}> { + let gltf: any; + let separateResources: Record; + const buffers: Record = {}; + + if (!fs.existsSync(entity.oDir)) { + fs.mkdirSync(entity.oDir, { recursive: true }); + } + + if (entity.isGLB) { + const buffer = await readFileBuffer(path.resolve(entity.iDir, entity.file)); + const result = await gltfPipe.glbToGltf(buffer, { + separate: true, + resourceDirectory: entity.iDir + }); + gltf = result.gltf; + separateResources = result.separateResources; + } else { + const json = await readFileJson(path.resolve(entity.iDir, entity.file)); + const result = await gltfPipe.processGltf(json, { + separate: true, + resourceDirectory: entity.iDir + }); + gltf = result.gltf; + separateResources = result.separateResources; + } + + for (const relativePath in separateResources) { + const outputPath = path.resolve(entity.oDir, relativePath); + if (/\.bin$/i.test(outputPath)) { + buffers[relativePath] = separateResources[relativePath]; + } + } + + for (const relativePath in buffers) { + delete separateResources[relativePath]; + } + + if (gltf.extensionsUsed?.includes('WX_processed_model')) { + showInfo('Model was already optimized before, skipping reprocess.'); + return { + gltf, + buffer: Object.values(buffers)[0] || Buffer.alloc(0), + assets: separateResources, + skip: true + }; + } + + showInfo('Parsed model successfully, start processing mesh data...'); + + const buffer = await processMeshes(gltf, buffers); + gltf.buffers = [{ uri: 'buffer.bin', byteLength: buffer.byteLength }]; + gltf.extensionsUsed = gltf.extensionsUsed || []; + gltf.extensionsUsed.push('WX_processed_model'); + + if (entity.compressTex !== 'off') { + gltf.extensionsUsed.push('WX_compressed_textures'); + } + + gltf.extensions = gltf.extensions || {}; + gltf.extensions.WX_processed_model = { + trustMaxAndMin: true, + simpleAttribNodeDontCheckTangent: true + }; + + return { gltf, buffer, assets: separateResources }; +} + +async function processMeshes(gltf: any, buffers: Record) { + if (!gltf.meshes) { + return Buffer.alloc(0); + } + + gltf.bufferViews.forEach((bufferView: any) => { + const sourceBuffer = buffers[gltf.buffers[bufferView.buffer].uri]; + const byteOffset = bufferView.byteOffset || 0; + const start = sourceBuffer.byteOffset + byteOffset; + const end = start + bufferView.byteLength; + bufferView.buffer = sourceBuffer.buffer.slice(start, end); + delete bufferView.byteOffset; + delete bufferView.byteLength; + }); + + const geometries: Record[] = []; + gltf.meshes.forEach((mesh: any) => { + const { primitives, mode } = mesh; + + if (mode && mode !== 4) { + showWarn('Mesh primitive mode is not TRIANGLES (4), skipping it.'); + return; + } + + if (!primitives) { + return; + } + + primitives.forEach((primitive: any) => { + const { attributes, targets } = primitive; + let index = 0; + + for (const geometry of geometries) { + let skip = false; + if (geometry.targets && geometry.targets !== targets) { + skip = true; + } + + if (!skip) { + for (const attrName in attributes) { + if (geometry[attrName] && geometry[attrName] !== attributes[attrName]) { + skip = true; + break; + } + } + } + + if (skip) { + index += 1; + continue; + } + + for (const attrName in attributes) { + if (!geometry[attrName]) { + geometry[attrName] = attributes[attrName]; + } + } + + if (!geometry.targets) { + geometry.targets = targets; + } + + primitive.geometry = index; + index += 1; + break; + } + + if (primitive.geometry === undefined) { + const geometry: any = {}; + for (const attrName in attributes) { + geometry[attrName] = attributes[attrName]; + } + if (targets) { + geometry.targets = targets; + } + geometries.push(geometry); + primitive.geometry = geometries.length - 1; + } + }); + }); + + gltf.meshes.forEach((mesh: any) => { + processMesh(mesh, gltf.accessors, gltf.materials || [], gltf.bufferViews, geometries); + }); + + showInfo('Generated missing normal/tangent data, start repacking geometry buffers...'); + + const geometryBuffers: { buffer: ArrayBuffer; stride: number }[] = []; + let geometryIndex = 0; + geometries.forEach((geometry) => { + geometryBuffers.push(...processGeometry(geometryIndex, geometry, gltf.accessors, gltf.bufferViews)); + geometryIndex = geometryBuffers.length; + }); + + showInfo('Repacked geometry data, combining final buffers...'); + return processBuffers(gltf.accessors, gltf.bufferViews, geometryBuffers); +} + +function processGeometry(index: number, geometry: any, accessors: any, bufferViews: any) { + let maxCount = 0; + let stride = 0; + const bytesAttrMap: { attrIdx: number; stride: number }[] = []; + + function calcAttr(attrIdx: number) { + const accessor = accessors[attrIdx]; + let size = getComponentByteLengthAndClass(accessor.componentType)[0] * getTypeLength(accessor.type); + size = size % 4 === 0 ? size : (Math.floor(size / 4) + 1) * 4; + bytesAttrMap.push({ attrIdx, stride: size }); + stride += size; + maxCount = Math.max(accessor.count, maxCount); + } + + Object.keys(geometry).forEach((attrKey) => { + if (attrKey === 'targets') { + geometry[attrKey].forEach((target: any) => { + Object.keys(target).forEach((key) => { + calcAttr(target[key]); + }); + }); + return; + } + + calcAttr(geometry[attrKey]); + }); + + if (stride > 252 * 2) { + throw new Error(`Too many vertex attributes, byteStride=${stride} exceeds supported split threshold.`); + } + + let subStride = 0; + let subMaps: { attrIdx: number; stride: number }[][] = []; + + function processAttr(attrIdx: number, subIndex: number, buffer: ArrayBuffer, offset: number) { + const accessor = accessors[attrIdx]; + const [componentByteLength, TypedArrayClass] = getComponentByteLengthAndClass(accessor.componentType); + const count = getTypeLength(accessor.type); + const perLen = subStride / componentByteLength; + const view = new TypedArrayClass(buffer, offset) as Float32Array; + const bufferView = bufferViews[accessor.bufferView]; + const origPerLen = bufferView ? (bufferView.byteStride || (componentByteLength * count)) / componentByteLength : count; + const origView = ( + bufferView + ? new TypedArrayClass(bufferView.buffer, accessor.byteOffset || 0) + : new TypedArrayClass(accessor.count * count) + ) as Float32Array; + + const max = new Array(count).fill(-Infinity); + const min = new Array(count).fill(Infinity); + + for (let i = 0; i < accessor.count; i += 1) { + for (let k = 0; k < count; k += 1) { + const value = origView[i * origPerLen + k]; + view[i * perLen + k] = value; + max[k] = Math.max(value, max[k]); + min[k] = Math.min(value, min[k]); + } + } + + accessor.geometryIndex = index + subIndex; + accessor.count = maxCount; + accessor.max = max; + accessor.min = min; + accessor.byteOffset = offset; + + const size = componentByteLength * count; + offset += size % 4 === 0 ? size : (Math.floor(size / 4) + 1) * 4; + return offset; + } + + if (stride <= 252) { + subStride = stride; + subMaps = [bytesAttrMap]; + } else { + bytesAttrMap.sort((a, b) => a.stride - b.stride).reverse(); + subMaps = [[], []]; + const sums = [0, 0]; + + bytesAttrMap.forEach((item) => { + const targetIndex = sums[0] <= sums[1] ? 0 : 1; + subMaps[targetIndex].push(item); + sums[targetIndex] += item.stride; + }); + + subStride = Math.max(sums[0], sums[1]); + const diff = sums[0] - sums[1]; + + if (subStride > 252 || diff % 4 !== 0 || Math.abs(diff) > 16) { + throw new Error(`Failed to split long vertex stride, subStride=${subStride}, diff=${diff}.`); + } + + if (diff !== 0) { + const subIndex = diff > 0 ? 1 : 0; + const paddingStride = Math.abs(diff); + accessors.push({ + componentType: 5126, + count: maxCount, + type: paddingStride === 4 + ? 'SCALAR' + : paddingStride === 8 + ? 'VEC2' + : paddingStride === 12 + ? 'VEC3' + : 'VEC4', + geometryIndex: index + subIndex + }); + subMaps[subIndex].push({ attrIdx: accessors.length - 1, stride: paddingStride }); + } + } + + const result = subMaps.map(() => ({ + stride: subStride, + buffer: Buffer.alloc(maxCount * subStride).buffer + })); + + subMaps.forEach((map, subIndex) => { + let offset = 0; + const buffer = result[subIndex].buffer; + map.forEach(({ attrIdx }) => { + offset = processAttr(attrIdx, subIndex, buffer, offset); + }); + }); + + return result; +} + +function processBuffers( + accessors: any, + bufferViews: any, + geometryBuffers: { buffer: ArrayBuffer; stride: number }[] +) { + let totalLen = 0; + const geoBufferViews = new Set(); + const bufferViewCache = new Set(); + + accessors.forEach((accessor: any) => { + if (accessor.geometryIndex !== undefined) { + if (accessor.bufferView !== undefined) { + geoBufferViews.add(accessor.bufferView); + } + if (!bufferViewCache.has(`g-${accessor.geometryIndex}`)) { + totalLen += geometryBuffers[accessor.geometryIndex].buffer.byteLength; + bufferViewCache.add(`g-${accessor.geometryIndex}`); + } + } else if (!bufferViewCache.has(accessor.bufferView)) { + totalLen += bufferViews[accessor.bufferView].buffer.byteLength; + bufferViewCache.add(accessor.bufferView); + } + + if (totalLen % 4) { + totalLen += 4 - (totalLen % 4); + } + }); + + const buffer = Buffer.alloc(totalLen); + const originalBufferViews = new Set(); + const reusableGeoBufferViews = Array.from(geoBufferViews); + const geoBufferViewCache: Record = {}; + bufferViewCache.clear(); + + let offset = 0; + accessors.forEach((accessor: any) => { + let sourceBuffer: ArrayBuffer; + let bufferView: any; + originalBufferViews.add(accessor.bufferView); + + if (accessor.geometryIndex === undefined && !bufferViewCache.has(accessor.bufferView)) { + bufferView = bufferViews[accessor.bufferView]; + sourceBuffer = bufferView.buffer; + bufferViewCache.add(accessor.bufferView); + } else if (accessor.geometryIndex !== undefined) { + const cacheKey = `g-${accessor.geometryIndex}`; + + if (geoBufferViewCache[cacheKey] !== undefined) { + accessor.bufferView = geoBufferViewCache[cacheKey]; + delete accessor.geometryIndex; + return; + } + + const { buffer: geometryBuffer, stride } = geometryBuffers[accessor.geometryIndex]; + delete accessor.geometryIndex; + bufferView = { + target: 34962, + byteStride: stride + }; + sourceBuffer = geometryBuffer; + + if (reusableGeoBufferViews.length) { + geoBufferViewCache[cacheKey] = reusableGeoBufferViews.shift()!; + } else { + bufferViews.push(null); + geoBufferViewCache[cacheKey] = bufferViews.length - 1; + } + + accessor.bufferView = geoBufferViewCache[cacheKey]; + bufferViews[accessor.bufferView] = bufferView; + } else { + return; + } + + buffer.set(Buffer.from(sourceBuffer), offset); + bufferView.byteOffset = offset; + bufferView.buffer = 0; + bufferView.byteLength = sourceBuffer.byteLength; + offset += sourceBuffer.byteLength; + + if (offset % 4) { + offset += 4 - (offset % 4); + } + }); + + accessors.forEach((accessor: any) => { + if (originalBufferViews.has(accessor.bufferView)) { + originalBufferViews.delete(accessor.bufferView); + } + }); + + originalBufferViews.forEach((bufferViewId) => { + delete bufferViews[bufferViewId].byteStride; + bufferViews[bufferViewId].buffer = 0; + bufferViews[bufferViewId].byteOffset = 0; + bufferViews[bufferViewId].byteLength = 1; + }); + + return buffer; +} + +function processMesh( + mesh: any, + accessors: any, + materials: any[], + bufferViews: any, + geometries: Record[] +) { + mesh.primitives?.forEach((primitive: any) => { + const { attributes, material, indices } = primitive; + + if (!attributes) { + return; + } + + const geometryIndex = primitive.geometry; + const geometry = geometries[geometryIndex]; + delete primitive.geometry; + + if (!geometry) { + return; + } + + const materialData = materials?.[material]; + if ((materialData?.normalTexture && geometry.TANGENT === undefined) || geometry.NORMAL === undefined) { + if (!geometry.TEXCOORD_0) { + showWarn('Cannot generate normal/tangent data because TEXCOORD_0 is missing.'); + } else if (indices === undefined) { + showWarn('Cannot generate normal/tangent data because indices are missing.'); + } else { + const indexAccessor = accessors[indices]; + const IndexArrayClass = getComponentByteLengthAndClass(indexAccessor.componentType)[1]; + const indexBuffer = new IndexArrayClass(bufferViews[indexAccessor.bufferView].buffer) as Uint16Array; + + if (geometry.NORMAL === undefined) { + showInfo('Generating vertex normals...'); + generateNormal(geometry, indexBuffer, bufferViews, accessors); + } + + if (geometry.TANGENT === undefined && materialData?.normalTexture) { + showInfo('Generating tangents...'); + generateTangent(geometry, indexBuffer, bufferViews, accessors); + } + } + } + + const { targets, ...attrs } = geometry; + primitive.attributes = attrs; + if (targets) { + primitive.targets = targets; + } + }); +} + +function generateNormal(geometry: Record, indexBuffer: Uint16Array, bufferViews: any, accessors: any) { + const { buffer: positions, per: positionStride, count } = getAttr(geometry.POSITION, bufferViews, accessors); + const normalsBuffer = Buffer.alloc(count * 3 * 4); + const normals = new Float32Array(normalsBuffer.buffer); + const vertexFaceNormals: vec3[][] = Array.from({ length: count }, () => []); + + for (let i = 0; i < indexBuffer.length; i += 3) { + const vertexIndexes = [indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]]; + const positionVectors = vertexIndexes.map((vertexIndex) => + new Float32Array(3).map((_, offset) => positions[vertexIndex * positionStride + offset]) + ); + + const edge0 = vec3.sub(vec3.create(), positionVectors[0], positionVectors[1]); + const edge1 = vec3.sub(vec3.create(), positionVectors[0], positionVectors[2]); + const normal = vec3.cross(vec3.create(), edge0, edge1); + + vertexIndexes.forEach((vertexIndex) => { + vertexFaceNormals[vertexIndex].push(normal); + }); + } + + vertexFaceNormals.forEach((faceNormals, index) => { + const normal = faceNormals.reduce((previous, current) => vec3.add(previous, previous, current), vec3.create()); + if (faceNormals.length > 0) { + vec3.scale(normal, normal, 1 / faceNormals.length); + } + if (vec3.length(normal) > 0) { + vec3.normalize(normal, normal); + } + normals.set(normal, index * 3); + }); + + bufferViews.push({ + buffer: normalsBuffer.buffer, + target: 34963, + byteOffset: 0, + byteLength: normalsBuffer.byteLength + }); + + accessors.push({ + bufferView: bufferViews.length - 1, + componentType: 5126, + count, + type: 'VEC3' + }); + + geometry.NORMAL = accessors.length - 1; +} + +function generateTangent(geometry: Record, indexBuffer: Uint16Array, bufferViews: any, accessors: any) { + const { buffer: positions, per: positionStride, count } = getAttr(geometry.POSITION, bufferViews, accessors); + const { buffer: uvs, per: uvStride } = getAttr(geometry.TEXCOORD_0, bufferViews, accessors); + const { buffer: normals, per: normalStride } = getAttr(geometry.NORMAL, bufferViews, accessors); + const tangentBuffer = Buffer.alloc(count * 4 * 4); + const tangents = new Float32Array(tangentBuffer.buffer); + const vertexFaceTangents: vec3[][] = Array.from({ length: count }, () => []); + + for (let i = 0; i < indexBuffer.length; i += 3) { + const vertexIndexes = [indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]]; + const positionVectors = vertexIndexes.map((vertexIndex) => + new Float32Array(3).map((_, offset) => positions[vertexIndex * positionStride + offset]) + ); + const uvVectors = vertexIndexes.map((vertexIndex) => + new Float32Array(2).map((_, offset) => uvs[vertexIndex * uvStride + offset]) + ); + + const edge0 = vec3.sub(vec3.create(), positionVectors[0], positionVectors[1]); + const edge1 = vec3.sub(vec3.create(), positionVectors[0], positionVectors[2]); + const deltaUV0 = vec2.sub(vec2.create(), uvVectors[0], uvVectors[1]); + const deltaUV1 = vec2.sub(vec2.create(), uvVectors[0], uvVectors[2]); + const det = deltaUV0[0] * deltaUV1[1] - deltaUV0[1] * deltaUV1[0]; + let tangent = vec3.create(); + + if (Math.abs(det) > 0) { + const xAxisInUVSpace = [deltaUV1[1], -deltaUV0[1]]; + tangent = vec3.add( + tangent, + vec3.scale(edge0, edge0, xAxisInUVSpace[0]), + vec3.scale(edge1, edge1, xAxisInUVSpace[1]) + ); + } + + vertexIndexes.forEach((vertexIndex) => { + vertexFaceTangents[vertexIndex].push(tangent); + }); + } + + vertexFaceTangents.forEach((faceTangents, index) => { + const tangent = faceTangents.reduce((previous, current) => vec3.add(previous, previous, current), vec3.create()); + if (faceTangents.length > 0) { + vec3.scale(tangent, tangent, 1 / faceTangents.length); + } + + const normal = new Float32Array(3).map((_, offset) => normals[index * normalStride + offset]); + const finalTangent = vec3.cross(tangent, tangent, normal); + + if (vec3.length(finalTangent) > 0) { + vec3.normalize(finalTangent, finalTangent); + } + + tangents.set(finalTangent, index * 4); + tangents[index * 4 + 3] = 1; + }); + + bufferViews.push({ + buffer: tangentBuffer.buffer, + target: 34963, + byteOffset: 0, + byteLength: tangentBuffer.byteLength + }); + + accessors.push({ + bufferView: bufferViews.length - 1, + componentType: 5126, + count, + type: 'VEC4' + }); + + geometry.TANGENT = accessors.length - 1; +} + +function getAttr(index: number, bufferViews: any, accessors: any) { + const { byteOffset = 0, bufferView, componentType, type, count } = accessors[index]; + const { buffer, byteStride } = bufferViews[bufferView]; + const stride = byteStride || (getComponentByteLengthAndClass(componentType)[0] * getTypeLength(type)); + + return { + buffer: new Float32Array(buffer, byteOffset), + per: stride / 4, + count + }; +} + +async function generateGLB( + gltf: any, + buffer: Buffer, + compressTex: boolean, + assets: Record +) { + let offset = buffer.byteLength; + const { buffer0, images } = combineImages(gltf, compressTex, offset, assets as Record); + + offset = buffer0.byteLength; + const bufferViews = gltf.bufferViews; + const assetPaths = Object.keys(assets); + const assetToBufferView: Record = {}; + const binaryChunks = [buffer0]; + + assetPaths.forEach((relativePath) => { + let assetBuffer = assets[relativePath]; + if (typeof assetBuffer === 'string') { + assetBuffer = Buffer.from(assetBuffer); + } + + assetBuffer = Buffer.concat([assetBuffer, align4(assetBuffer)]); + bufferViews.push({ buffer: 0, byteOffset: offset, byteLength: assetBuffer.byteLength }); + assetToBufferView[relativePath] = bufferViews.length - 1; + offset += assetBuffer.byteLength; + binaryChunks.push(assetBuffer); + }); + + gltf.shaders?.forEach((shader: any) => { + if (assetToBufferView[shader.uri] === undefined) { + return; + } + + shader.bufferView = assetToBufferView[shader.uri]; + delete shader.uri; + }); + + const mergedBinary = Buffer.concat(binaryChunks); + const finalBuffer = Buffer.concat([buffer, mergedBinary]); + const finalBufferAlign = align4(finalBuffer); + + gltf.buffers = [{ byteLength: buffer.byteLength + mergedBinary.byteLength }]; + if (images) { + images.forEach(({ uri, type, buffer: imageBuffer }) => { + gltf.buffers.push({ + uri, + byteLength: imageBuffer.length, + extensions: { + WX_compressed_textures: { type } + } + }); + }); + } + + const json = Buffer.from(JSON.stringify(gltf)); + const jsonAlign = align4(json); + + const glb = Buffer.concat([ + new Uint8Array(new Uint32Array([ + 0x46546c67, + 2, + 28 + json.byteLength + jsonAlign.byteLength + finalBuffer.byteLength + finalBufferAlign.byteLength, + json.byteLength + jsonAlign.byteLength, + 0x4e4f534a + ]).buffer), + json, + jsonAlign, + new Uint8Array(new Uint32Array([ + finalBuffer.byteLength + finalBufferAlign.byteLength, + 0x004e4942 + ]).buffer), + finalBuffer, + finalBufferAlign + ]); + + return { glb, images }; +} + +function getImageExtension(data: Buffer) { + const header = data.slice(0, 2); + const webpHeaderRIFF = data.slice(0, 4); + const webpHeaderWEBP = data.slice(8, 12); + + if (header.equals(Buffer.from([0x42, 0x4d]))) { + return '.bmp'; + } + if (header.equals(Buffer.from([0x47, 0x49]))) { + return '.gif'; + } + if (header.equals(Buffer.from([0xff, 0xd8]))) { + return '.jpg'; + } + if (header.equals(Buffer.from([0x89, 0x50]))) { + return '.png'; + } + if (header.equals(Buffer.from([0xab, 0x4b]))) { + return '.ktx'; + } + if (header.equals(Buffer.from([0x48, 0x78]))) { + return '.crn'; + } + if ( + webpHeaderRIFF.equals(Buffer.from([0x52, 0x49, 0x46, 0x46])) && + webpHeaderWEBP.equals(Buffer.from([0x57, 0x45, 0x42, 0x50])) + ) { + return '.webp'; + } + + throw new Error('Image data does not have a valid header.'); +} + +function combineImages( + gltf: any, + compressTex: boolean, + offset: number, + assets: Record +) { + const buffers: Buffer[] = new Array(compressTex ? 6 : 1).fill(Buffer.alloc(0)); + const bufferViews = gltf.bufferViews; + const sources = compressTex + ? { orig: 1, astc: 2, pvrtc: 3, etc: 4, s3tc: 5 } + : { orig: 0 }; + const offsets = { + orig: compressTex ? 0 : offset, + astc: 0, + pvrtc: 0, + etc: 0, + s3tc: 0 + }; + const types = compressTex + ? (['orig', 'astc', 'pvrtc', 'etc', 's3tc'] as const) + : (['orig'] as const); + + gltf.images?.forEach((image: any) => { + types.forEach((type) => { + const imageEntry = type === 'orig' ? image : image.extensions.WX_compressed_textures[type]; + let imageBuffer = assets[imageEntry.uri]; + imageBuffer = Buffer.concat([imageBuffer, align4(imageBuffer)]); + const sourceIndex = sources[type]; + buffers[sourceIndex] = Buffer.concat([buffers[sourceIndex], imageBuffer]); + bufferViews.push({ buffer: sourceIndex, byteOffset: offsets[type], byteLength: imageBuffer.byteLength }); + imageEntry.mimeType = (mime as any).getType(getImageExtension(imageBuffer)); + imageEntry.bufferView = bufferViews.length - 1; + offsets[type] += imageBuffer.byteLength; + delete assets[imageEntry.uri]; + delete imageEntry.uri; + }); + }); + + return { + buffer0: buffers[0], + images: !compressTex + ? undefined + : types.map((type) => ({ + type, + uri: `textures-${type}.bin`, + buffer: buffers[sources[type]] + })) + }; +} + +function align4(buffer: Buffer | ArrayBuffer) { + const delta = buffer.byteLength % 4; + return delta ? Buffer.alloc(4 - delta).fill(0x20) : Buffer.alloc(0); +} + +async function processTextures(entity: ModelEntity, gltf: any, assets: Record) { + const { images, textures, materials } = gltf; + if (!Array.isArray(textures) || !textures.length || !Array.isArray(images) || !images.length) { + return; + } + + for (const material of materials || []) { + const normalTexture = textures[material.normalTexture?.index]; + if (normalTexture) { + normalTexture.extras = normalTexture.extras || {}; + normalTexture.extras.isNormalMap = true; + } + } + + const processedImages = new Set(); + for (const texture of textures) { + let repeat = true; + + if (texture.sampler !== undefined) { + const sampler = gltf.samplers?.[texture.sampler] || {}; + const wrapS = sampler.wrapS || 10497; + const wrapT = sampler.wrapT || 10497; + repeat = wrapS === 10497 || wrapT === 10497; + } + + if (processedImages.has(texture.source)) { + continue; + } + + const image = images[texture.source]; + const relativePath: string = image.uri; + + if (repeat || entity.compressTex !== 'off') { + assets[relativePath] = await checkPOTandConvert(assets[relativePath], relativePath); + } + + if (entity.compressTex !== 'off') { + const compressed = await compressTexture( + relativePath, + assets[relativePath], + entity.compressTex, + !!image.extras?.isNormalMap + ); + const basePath = relativePath.replace(path.extname(relativePath), ''); + image.extensions = image.extensions || {}; + const textureExtension = image.extensions.WX_compressed_textures = {}; + + (['astc', 'etc', 'pvrtc', 's3tc'] as const).forEach((type) => { + const uri = `${basePath}-${type}.ktx`; + assets[uri] = compressed[type]; + textureExtension[type] = { uri }; + }); + } + + processedImages.add(texture.source); + } +} + +async function checkPOTandConvert(imageBuffer: Buffer, relativePath: string): Promise { + const info = readImageMetadata(imageBuffer); + if (!info.width || !info.height) { + showWarn(`Cannot inspect image size for '${relativePath}', skip POT resize.`); + return imageBuffer; + } + + const width = getPOT(info.width); + const height = getPOT(info.height); + + if (width === info.width && height === info.height) { + return imageBuffer; + } + + showInfo(`Resize repeat texture to POT: '${relativePath}' [${info.width}, ${info.height}] -> [${width}, ${height}]`); + const resized = await resizeImageBuffer(imageBuffer, relativePath, width, height); + + if (!resized) { + showWarn(`Image format of '${relativePath}' is not supported by the JS resizer, keep original texture.`); + return imageBuffer; + } + + return resized; +} + +function getPOT(size: number): number { + const log = Math.log2(size); + const integer = ~~log; + const delta = log % 1; + return Math.pow(2, delta < 0.4 ? integer : integer + 1); +} + +async function execOne(entity: ModelEntity) { + showInfo(`Start processing ${path.join(entity.iDir, entity.file)}`); + + const { gltf, buffer, assets, skip } = await processGlTF(entity); + if (skip) { + throw new Error('Model has already been optimized and cannot be optimized twice.'); + } + + showInfo('Processing textures and optional compression...'); + await processTextures(entity, gltf, assets); + + let outputFile = ''; + if (entity.toGLB) { + showInfo('Generating GLB...'); + const { glb, images } = await generateGLB(gltf, buffer, entity.compressTex !== 'off', assets); + outputFile = path.resolve(entity.oDir, 'index.glb'); + await writeFile(outputFile, glb); + + if (images) { + for (const image of images) { + await writeFile(path.resolve(entity.oDir, image.uri), image.buffer); + } + } + + showInfo('GLB generation complete.'); + } else { + showInfo(`Writing optimized output to ${entity.oDir}`); + outputFile = path.resolve(entity.oDir, 'index.gltf'); + await writeFile(outputFile, JSON.stringify(gltf, undefined, 2)); + await writeFile(path.resolve(entity.oDir, 'buffer.bin'), buffer); + + for (const relativePath in assets) { + await writeFile(path.resolve(entity.oDir, relativePath), assets[relativePath]); + } + + showInfo('Output files written.'); + } + + showInfo( + `Completed ${path.join(entity.iDir, entity.file)} -> ${path.join(entity.oDir, `index.${entity.toGLB || entity.isGLB ? 'glb' : 'gltf'}`)}` + ); + + return outputFile; +} + +export async function optimizeModel( + input: string, + output: string, + compressTex: CompressTex = 'off', + toGLB = false +) { + if (!input || !output) { + throw new Error('Both input and output paths are required.'); + } + + if (!fs.existsSync(input)) { + throw new Error(`Input path does not exist: '${input}'.`); + } + + if (!fs.existsSync(output)) { + throw new Error(`Output path does not exist: '${output}'.`); + } + + if (!isGLTFFile(input)) { + throw new Error(`'${input}' is not a .gltf or .glb file.`); + } + + const entity = getEntity(input, output); + entity.toGLB = toGLB; + entity.compressTex = compressTex; + + return execOne(entity); +} + +export async function optimizeModelWithOptions(options: OptimizeModelOptions) { + return optimizeModel( + options.input, + options.output, + options.compressTex || 'off', + !!options.toGLB + ); +} + +export default optimizeModel; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..dd914da --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,65 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +export function showWarn(message: string) { + console.error('\x1b[33m%s\x1b[0m', `Warn: ${message}`); +} + +export function showInfo(message: string) { + console.info('\x1b[32m%s\x1b[0m', message); +} + +const GLTF_EXTENSIONS = new Set(['.gltf', '.glb']); + +export function isGLTFFile(filePath: string) { + return GLTF_EXTENSIONS.has(path.extname(filePath).toLowerCase()); +} + +export async function readFileJson(filePath: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(filePath, { encoding: 'utf8' }, (error, content) => { + if (error) { + reject(error); + return; + } + + resolve(JSON.parse(content)); + }); + }); +} + +export async function readFileBuffer(filePath: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(filePath, (error, content) => { + if (error) { + reject(error); + return; + } + + resolve(content); + }); + }); +} + +export async function writeFile(filePath: string, buffer: Buffer | string): Promise { + return new Promise((resolve, reject) => { + const dirPath = path.dirname(filePath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + + fs.writeFile( + filePath, + buffer, + typeof buffer === 'string' ? { encoding: 'utf8' } : {}, + (error) => { + if (error) { + reject(error); + return; + } + + resolve(filePath); + } + ); + }); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d0e55aa --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": [ + "src/**/*" + ] +}