diff --git a/.github/renovate.json b/.github/renovate.json index aaf421f5f54..cd715e49af7 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -14,13 +14,37 @@ ] }, { - "matchDepTypes": ["devDependencies"], + "matchDepTypes": [ + "devDependencies" + ], "rangeStrategy": "pin" }, { - "matchDepTypes": ["dependencies"], + "matchDepTypes": [ + "dependencies" + ], "rangeStrategy": "widen" + }, + { + "matchPackageNames": [ + "sinon" + ], + "matchFileNames": [ + "package.json" + ], + "enabled": false + }, + { + "matchPackageNames": [ + "monaco-editor" + ], + "matchFileNames": [ + "examples/package.json" + ], + "enabled": false } ], - "ignorePaths": [".nvmrc"] + "ignorePaths": [ + ".nvmrc" + ] } diff --git a/.github/workflows/beta.yaml b/.github/workflows/beta.yml similarity index 93% rename from .github/workflows/beta.yaml rename to .github/workflows/beta.yml index 9b3f75f6027..49e072f780a 100644 --- a/.github/workflows/beta.yaml +++ b/.github/workflows/beta.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: force: - description: 'Force release even if no changes detected' + description: "Force release even if no changes detected" type: boolean default: false @@ -24,7 +24,7 @@ jobs: private-key: ${{ secrets.APP_KEY }} - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2da723c1020..ae7797936fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,9 @@ name: CI on: workflow_dispatch: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] concurrency: group: ci-${{ github.event.pull_request.number || github.ref }} @@ -20,132 +20,132 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - name: Checkout code - uses: actions/checkout@v5 + - name: Checkout code + uses: actions/checkout@v6 - - name: Setup Node.js 22.x - uses: actions/setup-node@v6 - with: - node-version: 24.x - cache: 'npm' + - name: Setup Node.js 22.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "npm" - - name: Install dependencies - run: npm clean-install --progress=false --no-fund + - name: Install dependencies + run: npm clean-install --progress=false --no-fund - - name: Build PlayCanvas - run: npm run build + - name: Build PlayCanvas + run: npm run build - - name: Run Publint - run: npm run publint + - name: Run Publint + run: npm run publint docs: name: Docs runs-on: ubuntu-latest timeout-minutes: 10 steps: - - name: Checkout code - uses: actions/checkout@v5 + - name: Checkout code + uses: actions/checkout@v6 - - name: Setup Node.js 22.x - uses: actions/setup-node@v6 - with: - node-version: 24.x - cache: 'npm' + - name: Setup Node.js 22.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "npm" - - name: Install dependencies - run: npm clean-install --progress=false --no-fund + - name: Install dependencies + run: npm clean-install --progress=false --no-fund - - name: Build API reference manual - run: npm run docs + - name: Build API reference manual + run: npm run docs lint: name: Lint runs-on: ubuntu-latest timeout-minutes: 10 steps: - - name: Checkout code - uses: actions/checkout@v5 + - name: Checkout code + uses: actions/checkout@v6 - - name: Setup Node.js 22.x - uses: actions/setup-node@v6 - with: - node-version: 24.x - cache: 'npm' + - name: Setup Node.js 22.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "npm" - - name: Install dependencies - run: npm clean-install --progress=false --no-fund + - name: Install dependencies + run: npm clean-install --progress=false --no-fund - - name: Run ESLint - run: npm run lint + - name: Run ESLint + run: npm run lint - - name: Run ESLint on examples - working-directory: ./examples - run: | - npm clean-install --progress=false --no-fund - npm run lint + - name: Run ESLint on examples + working-directory: ./examples + run: | + npm clean-install --progress=false --no-fund + npm run lint typescript-declarations: name: TypeScript Declarations runs-on: ubuntu-latest timeout-minutes: 10 steps: - - name: Checkout code - uses: actions/checkout@v5 + - name: Checkout code + uses: actions/checkout@v6 - - name: Setup Node.js 22.x - uses: actions/setup-node@v6 - with: - node-version: 24.x - cache: 'npm' + - name: Setup Node.js 22.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "npm" - - name: Install dependencies - run: npm clean-install --progress=false --no-fund + - name: Install dependencies + run: npm clean-install --progress=false --no-fund - - name: Build TypeScript declarations - run: npm run build:types + - name: Build TypeScript declarations + run: npm run build:types - - name: Compile TypeScript declarations - run: npm run test:types + - name: Compile TypeScript declarations + run: npm run test:types unit-test: name: Unit Test runs-on: ubuntu-latest timeout-minutes: 10 steps: - - name: Checkout code - uses: actions/checkout@v5 + - name: Checkout code + uses: actions/checkout@v6 - - name: Setup Node.js 22.x - uses: actions/setup-node@v6 - with: - node-version: 24.x - cache: 'npm' + - name: Setup Node.js 22.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "npm" - - name: Install dependencies - run: npm clean-install --progress=false --no-fund + - name: Install dependencies + run: npm clean-install --progress=false --no-fund - - name: Run unit tests - run: npm test + - name: Run unit tests + run: npm test build-examples: name: Build Examples Browser runs-on: ubuntu-latest timeout-minutes: 10 steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Node.js 22.x - uses: actions/setup-node@v6 - with: - node-version: 24.x - cache: 'npm' - - - name: Install dependencies - run: npm clean-install --progress=false --no-fund - - - name: Build Examples Browser - working-directory: ./examples - run: | - npm clean-install --progress=false --no-fund - npm run build + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js 22.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "npm" + + - name: Install dependencies + run: npm clean-install --progress=false --no-fund + + - name: Build Examples Browser + working-directory: ./examples + run: | + npm clean-install --progress=false --no-fund + npm run build diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yml similarity index 65% rename from .github/workflows/publish.yaml rename to .github/workflows/publish.yml index 7d21cfd5999..cc230d88be8 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yml @@ -3,24 +3,28 @@ name: Publish on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - 'v[0-9]+.[0-9]+.[0-9]+-preview.[0-9]+' - - 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-preview.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" jobs: publish: + name: Publish runs-on: ubuntu-latest - if: github.repository_owner == 'playcanvas' + if: ${{ github.repository_owner == 'playcanvas' }} + permissions: + contents: read + id-token: write steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - - name: Set up Node.js 22.x + - name: Set up Node.js 24.x uses: actions/setup-node@v6 with: node-version: 24.x - cache: 'npm' - registry-url: 'https://registry.npmjs.org/' + cache: "npm" + registry-url: "https://registry.npmjs.org/" - name: Parse tag name run: | @@ -46,15 +50,13 @@ jobs: else tag=latest fi - npm publish --tag $tag - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + npm publish --tag $tag --provenance - name: Write version run: echo "${{ env.VERSION }}" > version.txt - name: Upload version - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: version - path: version.txt \ No newline at end of file + path: version.txt diff --git a/.github/workflows/upload.yaml b/.github/workflows/upload.yml similarity index 95% rename from .github/workflows/upload.yaml rename to .github/workflows/upload.yml index f6591065e33..af4da807ba2 100644 --- a/.github/workflows/upload.yaml +++ b/.github/workflows/upload.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Download version if: github.event_name == 'workflow_run' - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: version github-token: ${{ secrets.GITHUB_TOKEN }} @@ -36,4 +36,4 @@ jobs: -d '{ "engineVersion": "${{ env.VERSION }}" }' ${{ secrets.PUBLISH_ENDPOINT }}; then echo "Failed to publish to code.playcanvas.com" exit 1 - fi \ No newline at end of file + fi diff --git a/.gitignore b/.gitignore index da5e5a75ce7..63cf3d3e923 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.DS_Store +.cursor .idea/ .vscode/ build @@ -14,3 +15,6 @@ stats.html .npmrc examples/.npmrc .prettierrc +# Added by Snap Cursor Rules extension +.cursor/rules/cursor-rules-debug.log +.cursor/rules/remote diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..3a3090772d2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,342 @@ +# Agent Guidelines for PlayCanvas Engine + +This document contains rules, conventions, and best practices for AI agents and developers working on the PlayCanvas Engine codebase. + +## Project Overview + +PlayCanvas is an open-source WebGL/WebGPU game engine written in JavaScript. It's a performance-critical library used by thousands of developers worldwide. + +- **Language**: JavaScript (ES2022) with JSDoc for TypeScript type definitions +- **Module System**: ES Modules +- **Node Version**: >=18.0.0 +- **Build System**: Rollup +- **Testing**: Mocha + Chai + Sinon +- **Linting**: ESLint with @playcanvas/eslint-config +- **License**: MIT + +## General Code Rules + +### 1. Code Style and Formatting + +- **Follow ESLint rules**: Always run `npm run lint` before committing + - **Important**: Only fix lint issues in code you are actively modifying or creating + - Do not fix pre-existing lint issues in unrelated code unless specifically asked + - Focus on ensuring new and refactored code is lint-free +- **Use JSDoc comments**: All public APIs must have comprehensive JSDoc documentation +- **Module imports**: Use ES6 import/export syntax +- **Naming conventions**: + - Classes: PascalCase (e.g., `GraphicsDevice`, `Entity`) + - Functions/methods: camelCase (e.g., `createShader`, `setPosition`) + - Constants: UPPER_SNAKE_CASE (e.g., `PIXELFORMAT_RGBA8`) + +### 2. File Organization + +- **Source files**: All engine source code goes in `src/` +- **Directory structure**: + - `src/core/` - Core utilities and data structures + - `src/platform/` - Platform-specific code (graphics, audio, input) + - `src/scene/` - Scene graph, rendering, materials, shaders + - `src/framework/` - High-level components and application framework + - `src/extras/` - Optional extras and utilities +- **Build output**: Generated files go in `build/` (never edit these directly) +- **Examples**: Live in `examples/src/examples/` +- **Tests**: Unit tests go in `test/` with `.mjs` extension +- **File naming**: Module file names should match the main class they contain + - Use kebab-case for file names (e.g., `graphics-device.js` for `GraphicsDevice` class) + - If a class is renamed, the file should be renamed to match + - Multiple related classes can share a file if they're tightly coupled + +#### Module Dependency Hierarchy + +The codebase follows a strict hierarchical structure to maintain clean architecture: + +``` +core → platform → scene → framework +``` + +**Rules**: +- Lower-level modules **cannot import** from higher-level modules +- Lower-level modules **cannot use instances** from higher-level modules +- Example: `core/` cannot import from `platform/`, `scene/`, or `framework/` +- Example: `scene/` cannot import from `framework/` + +**Known Exception**: +- `CameraComponent` (from `framework/`) is currently used in multiple places at the `scene/` level +- **Do not introduce new exceptions** unless explicitly requested and confirmed +- When in doubt, ask before breaking the hierarchy + +This hierarchy ensures: +- Clean separation of concerns +- Prevents circular dependencies +- Makes the codebase more maintainable and testable + +### 3. Documentation Standards + +- **JSDoc is mandatory** for all public APIs: + ```javascript + /** + * Brief description of the function. + * + * @param {string} name - Parameter description. + * @param {number} [optional=0] - Optional parameter with default. + * @returns {boolean} Return value description. + * @example + * const result = myFunction('test', 5); + */ + ``` +- **Include examples** for complex APIs +- **Document side effects**: Mention if a function modifies state +- **Link related APIs**: Use `@see` tags to cross-reference +- **Mark deprecations**: Use `@deprecated` with migration instructions + +### 4. TypeScript Definitions + +- JSDoc comments are used to generate TypeScript definitions +- Run `npm run build:types` to generate `.d.ts` files +- Test types with `npm run test:types` +- Use proper JSDoc type annotations: + - `@type {TypeName}` for variables + - `@param {TypeName} paramName` for parameters + - `@returns {TypeName}` for return values + - Support for generics, unions, and complex types +- **Type-only imports**: Use `@import` for types referenced in JSDoc comments + - These imports are only for type information, not runtime code + - Place at the top of the file in a JSDoc comment block + - Example: + ```javascript + /** + * @import { Texture } from './texture.js' + * @import { Shader } from './shader.js' + */ + ``` + - These help TypeScript understand types without adding runtime dependencies + +### 5. Testing + +- **Write tests** for all new features and bug fixes if instructed +- **Test location**: `test/` directory, organized by module +- **Test naming**: Use descriptive names that explain what is being tested +- **Run tests**: `npm test` (or `npm run test:coverage` for coverage) +- **Test structure**: + ```javascript + describe('ClassName', function () { + describe('#methodName', function () { + it('should do something specific', function () { + // Test implementation + }); + }); + }); + ``` + +### 6. Performance Considerations + +This is a **performance-critical** engine. Always consider: + +- **Avoid allocations in hot paths**: Reuse objects, use object pools +- **Minimize function calls**: Inline critical code when necessary +- **Cache property access**: Store frequently accessed properties in local variables +- **Use typed arrays**: For numeric data (Float32Array, Uint8Array, etc.) + +### 7. Graphics API Considerations + +- **Multi-backend support**: Code must work with both WebGL2 and WebGPU +- **Use abstraction layers**: Don't call WebGL/WebGPU APIs directly in high-level code +- **Shader code**: Maintain both GLSL and WGSL versions + - GLSL: `src/scene/shader-lib/glsl/` + - WGSL: `src/scene/shader-lib/wgsl/` +- **NullGraphicsDevice**: A dummy graphics device for headless/testing scenarios + - When adding public API methods to `GraphicsDevice`, add stub implementations to `NullGraphicsDevice` + - Stub methods should be empty or return safe default values to avoid crashes + - This ensures the engine can run without a real graphics backend for testing/server-side use + +## Project-Specific Rules + +### 8. API Stability and Deprecation + +- **Backward compatibility matters**: Breaking changes require major version bump +- **Deprecation process**: + 1. Mark API as `@deprecated` with alternatives + 2. Add console warning in development builds + 3. Keep deprecated code for at least one major version + 4. Consider removing jsdocs completely +- **Never remove public APIs** without proper deprecation cycle + +### 9. Build System + +- **Source is in `src/`**: Never edit files in `build/` directory +- **Module exports**: Main exports defined in `src/index.js` + +### 10. Dependencies + +- **Minimal dependencies**: Avoid adding new dependencies unless absolutely necessary +- **Types only**: `@types/*` and `@webgpu/types` are the main dependencies + +### 11. Error Handling + +- **Debug class**: Use `Debug` class (`src/core/debug.js`) for logging and assertions + - Methods include: `assert()`, `warn()`, `warnOnce()`, `error()`, `deprecated()`, `log()`, `trace()` + - **Important**: All Debug methods are stripped out in production builds + - Use `*Once()` variants to avoid spam in loops or frequent calls + - Don't use Debug in hot paths - even in debug builds, excessive logging impacts performance +- **DebugHelper class**: Helper methods for debugging (also stripped in production) + - `setName()`, `setLabel()`, `setDestroyed()` for marking objects + +### 12. Code Comments + +- **Explain "why" not "what"**: Code should be self-documenting, but comments help with quick understanding +- **Complex algorithms**: Explain the approach and any non-obvious optimizations +- **TODOs**: Include issue reference or context + ```javascript + // TODO: Optimize this when texture streaming is implemented (#1234) + ``` +- **Avoid very obvious comments**: Don't state what the code clearly does + +### 13. Commit and PR Guidelines + +- **Clear commit messages**: Use conventional commits format + - `feat: Add feature description` + - `fix: Bug fix description` + - `perf: Performance improvement description` + - `docs: Documentation update` + - `refactor: Code refactoring` + - `test: Test updates` +- **Reference issues**: Include issue number in commit message in format 'Fixed #1234' +- **Small, focused commits**: Each commit should be a logical unit +- **No generated files**: Don't commit files in `build/` directory + +### 14. Browser Compatibility + +- **Modern browsers only**: ES6+ features are allowed +- **No polyfills in engine**: Users can add their own if needed (except `src/polyfill/`) +- **WebGL 2.0 minimum**: WebGL 1.0 is not supported +- **WebGPU support**: Must maintain compatibility with WebGPU API + +## Common Patterns + +### 15. Object Creation + +```javascript +// Prefer class syntax with TypeScript-like property declarations +class MyClass { + /** + * @type {GraphicsDevice} + */ + device; + + /** + * @type {string} + */ + name; + + constructor(device, options = {}) { + this.device = device; + this.name = options.name ?? 'default'; + } + + destroy() { + // Clean up resources + this.device = null; + } +} +``` + +### 16. Resource Management + +```javascript +// Always provide destroy() method for objects holding resources +class Resource { + constructor() { + this._resource = createResource(); + } + + destroy() { + this._resource?.destroy(); + this._resource = null; + } +} +``` + +### 17. Root Cause Analysis + +Always address the root cause of issues rather than implementing workarounds that hide or suppress problems: + +- **Identify the root cause**: When you encounter an error or unexpected behavior, investigate why it's happening +- **Don't mask symptoms**: Avoid solutions that simply hide errors or suppress warnings without fixing the underlying issue +- **Fix at the source**: When you identify the root cause, fix it where the problem originates, not where it manifests + +## Things to Avoid + +### 18. Anti-Patterns + +- **Don't use `var`**: Use `const` or `let` (except in legacy `scripts/` directory) +- **Avoid `any` types**: Be specific in JSDoc type annotations +- **No global state**: Everything should be instance-based + - Exception: Module-scope variables for local optimization are allowed (e.g., reusable Mat4 instances) + - These must never be exported and should only be used within the module +- **Don't bypass abstractions**: Use the platform API, not direct WebGL/WebGPU calls +- **Don't suppress linter warnings**: Fix the underlying issue + +### 19. Performance Anti-Patterns + +- **No allocations in render loop**: Pre-allocate and reuse if feasible +- **Don't use `try/catch` in hot paths**: It prevents optimizations +- **No string concatenation in loops**: Build arrays and join +- **Don't create functions in loops**: Define functions outside + +## AI Agent-Specific Guidelines + +### 20. When Making Changes + +- **Read existing code first**: Understand the context and patterns +- **Follow existing style**: Match the style of surrounding code +- **Lint your changes**: Run `npm run lint` +- **Update documentation**: Modify JSDoc comments when changing APIs +- **Consider performance**: This is a real-time engine, every microsecond counts +- **Check both WebGL and WebGPU**: Changes may affect both backends + +### 21. When Creating Examples + +- Examples go in `examples/src/examples/` +- Follow existing example structure (see other `.example.mjs` files) +- Include descriptive comments +- Keep examples simple and focused on one feature + +### 22. When Writing PR Descriptions + +- **Format as a single code block**: Always deliver PR descriptions wrapped in triple backticks for easy copy/paste +- **Structure**: + - Brief title and overview + - Bullet points for functionality changes + - Technical details section (if relevant) + - **Clearly list all public API changes** with before/after code examples + - List updated examples (if applicable) + - Performance considerations (if relevant) +- **Focus on user-facing changes**: What developers using the engine will see/use +- **Be concise but complete**: Include all breaking changes and new APIs +- **Avoid excessive detail**: Group related changes together, don't list every tiny implementation detail or internal refactoring +- **Only document public APIs**: Do not list functionality tagged with `@ignore`, `@protected`, or `@private` as these are internal implementation details + +## Resources + +- **API Reference**: https://api.playcanvas.com/engine/ +- **User Manual**: https://developer.playcanvas.com/user-manual/engine/ +- **Developer Site**: https://github.com/playcanvas/developer-site + - For large features, ask to add documentation to the User Manual + - Manual pages are Markdown files in the `docs/` directory +- **Examples**: https://playcanvas.github.io +- **Forum**: https://forum.playcanvas.com +- **Discord**: https://discord.gg/RSaMRzg +- **GitHub Issues**: https://github.com/playcanvas/engine/issues + +## Questions? + +When in doubt: +1. Look at similar existing code in the codebase +2. Check the ESLint configuration +3. Review recent commits for patterns +4. If unclear or multiple valid approaches exist, ask instead of picking a possibly incorrect solution + +--- + +**Remember**: This is a library used by thousands of developers. Quality, performance, and stability are paramount. When in doubt, prefer conservative, well-tested changes over clever optimizations. + diff --git a/examples/assets/hdri/shanghai-riverside-4k.hdr b/examples/assets/hdri/shanghai-riverside-4k.hdr new file mode 100644 index 00000000000..a10b6063c31 Binary files /dev/null and b/examples/assets/hdri/shanghai-riverside-4k.hdr differ diff --git a/examples/assets/models/AttenuationTest.glb b/examples/assets/models/AttenuationTest.glb new file mode 100644 index 00000000000..070457de46c Binary files /dev/null and b/examples/assets/models/AttenuationTest.glb differ diff --git a/examples/assets/models/jet-fighter.glb b/examples/assets/models/jet-fighter.glb new file mode 100644 index 00000000000..219b2f2244a Binary files /dev/null and b/examples/assets/models/jet-fighter.glb differ diff --git a/examples/assets/models/jet-fighter.txt b/examples/assets/models/jet-fighter.txt new file mode 100644 index 00000000000..3844940a4af --- /dev/null +++ b/examples/assets/models/jet-fighter.txt @@ -0,0 +1,5 @@ +Mitsubishi F-2 - Fighter Jet - Free by bohmerang on Sketchfab: + +https://sketchfab.com/3d-models/mitsubishi-f-2-fighter-jet-free-d3d7244554974f499b106e6c11fe3aaf + +CC BY 4.0 https://creativecommons.org/licenses/by/4.0/ diff --git a/examples/assets/scripts/misc/hatch-material.mjs b/examples/assets/scripts/misc/hatch-material.mjs index f2d62c1d157..dff0c0ddea7 100644 --- a/examples/assets/scripts/misc/hatch-material.mjs +++ b/examples/assets/scripts/misc/hatch-material.mjs @@ -89,6 +89,68 @@ const createHatchMaterial = (device, textures) => { gl_Position = matrix_viewProjection * worldPos; } `, + vertexWGSL: /* wgsl */ ` + + // include code transform shader functionality provided by the engine. It automatically + // declares vertex_position attribute, and handles skinning and morphing if necessary. + // It also adds uniforms: matrix_viewProjection, matrix_model, matrix_normal. + // Functions added: getModelMatrix, getLocalPosition + #include "transformCoreVS" + + // include code for normal shader functionality provided by the engine. It automatically + // declares vertex_normal attribute, and handles skinning and morphing if necessary. + // Functions added: getNormalMatrix, getLocalNormal + #include "normalCoreVS" + + // add additional attributes we need + attribute aUv0: vec2f; + + // engine supplied uniforms + uniform view_position: vec3f; + + // out custom uniforms + uniform uLightDir: vec3f; + uniform uMetalness: f32; + + // variables we pass to the fragment shader + varying uv0: vec2f; + varying brightness: f32; + + @vertex + fn vertexMain(input: VertexInput) -> VertexOutput + { + var output: VertexOutput; + + // use functionality from transformCore to get a world position, which includes skinning and morphing as needed + let modelMatrix: mat4x4f = getModelMatrix(); + let localPos: vec3f = getLocalPosition(vertex_position.xyz); + let worldPos: vec4f = modelMatrix * vec4f(localPos, 1.0); + + // use functionality from normalCore to get the world normal, which includes skinning and morphing as needed + let normalMatrix: mat3x3f = getNormalMatrix(modelMatrix); + let localNormal: vec3f = getLocalNormal(vertex_normal); + let worldNormal: vec3f = normalize(normalMatrix * localNormal); + + // simple wrap-around diffuse lighting using normal and light direction + let diffuse: f32 = dot(worldNormal, uniform.uLightDir) * 0.5 + 0.5; + + // a simple specular lighting + let viewDir: vec3f = normalize(uniform.view_position - worldPos.xyz); + let reflectDir: vec3f = reflect(-uniform.uLightDir, worldNormal); + let specular: f32 = pow(max(dot(viewDir, reflectDir), 0.0), 9.0); + + // combine the lighting + output.brightness = diffuse * (1.0 - uniform.uMetalness) + specular * uniform.uMetalness; + + // Pass the texture coordinates + output.uv0 = aUv0; + + // Transform the geometry + output.position = uniform.matrix_viewProjection * worldPos; + + return output; + } + `, fragmentGLSL: /* glsl */ ` // this gives us gamma correction functions, such as gammaCorrectOutput #include "gammaPS" @@ -133,6 +195,56 @@ const createHatchMaterial = (device, textures) => { gl_FragColor.a = 1.0; } `, + fragmentWGSL: /* wgsl */ ` + // this gives us gamma correction functions, such as gammaCorrectOutput + #include "gammaPS" + + // this give us tonemapping functionality: toneMap + #include "tonemappingPS" + + // this gives us for functionality: addFog + #include "fogPS" + + varying brightness: f32; + varying uv0: vec2f; + + var uDiffuseMap: texture_2d_array; + var uDiffuseMapSampler: sampler; + uniform uDensity: f32; + uniform uNumTextures: f32; + uniform uColor: vec3f; + + @fragment + fn fragmentMain(input: FragmentInput) -> FragmentOutput + { + var output: FragmentOutput; + var colorLinear: vec3f; + + #ifdef TOON + + // just a simple toon shader - no texture sampling + let level: f32 = f32(i32(input.brightness * uniform.uNumTextures)) / uniform.uNumTextures; + colorLinear = level * uniform.uColor; + + #else + // brightness dictates the hatch texture level + let level: f32 = (1.0 - input.brightness) * uniform.uNumTextures; + + // sample the two nearest levels and interpolate between them + let hatchUnder: vec3f = textureSample(uDiffuseMap, uDiffuseMapSampler, input.uv0 * uniform.uDensity, i32(floor(level))).xyz; + let hatchAbove: vec3f = textureSample(uDiffuseMap, uDiffuseMapSampler, input.uv0 * uniform.uDensity, i32(min(ceil(level), uniform.uNumTextures - 1.0))).xyz; + colorLinear = mix(hatchUnder, hatchAbove, fract(level)) * uniform.uColor; + #endif + + // handle standard color processing - the called functions are automatically attached to the + // shader based on the current fog / tone-mapping / gamma settings + let fogged: vec3f = addFog(colorLinear); + let toneMapped: vec3f = toneMap(fogged); + output.color = vec4f(gammaCorrectOutput(toneMapped), 1.0); + + return output; + } + `, attributes: { vertex_position: SEMANTIC_POSITION, vertex_normal: SEMANTIC_NORMAL, diff --git a/examples/assets/splats/flipbook/monkey_0001.sog b/examples/assets/splats/flipbook/monkey_0001.sog new file mode 100644 index 00000000000..a06adda0ff1 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0001.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0002.sog b/examples/assets/splats/flipbook/monkey_0002.sog new file mode 100644 index 00000000000..a1e530b2a08 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0002.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0003.sog b/examples/assets/splats/flipbook/monkey_0003.sog new file mode 100644 index 00000000000..a624ffbf617 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0003.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0004.sog b/examples/assets/splats/flipbook/monkey_0004.sog new file mode 100644 index 00000000000..d5cd17a8c54 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0004.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0005.sog b/examples/assets/splats/flipbook/monkey_0005.sog new file mode 100644 index 00000000000..0eb8c16fa97 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0005.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0006.sog b/examples/assets/splats/flipbook/monkey_0006.sog new file mode 100644 index 00000000000..0060d3f35c6 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0006.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0007.sog b/examples/assets/splats/flipbook/monkey_0007.sog new file mode 100644 index 00000000000..45471bafc34 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0007.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0008.sog b/examples/assets/splats/flipbook/monkey_0008.sog new file mode 100644 index 00000000000..09437da3fd1 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0008.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0009.sog b/examples/assets/splats/flipbook/monkey_0009.sog new file mode 100644 index 00000000000..c1eb656c320 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0009.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0010.sog b/examples/assets/splats/flipbook/monkey_0010.sog new file mode 100644 index 00000000000..4e5ae40f94e Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0010.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0011.sog b/examples/assets/splats/flipbook/monkey_0011.sog new file mode 100644 index 00000000000..592782e9f71 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0011.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0012.sog b/examples/assets/splats/flipbook/monkey_0012.sog new file mode 100644 index 00000000000..b7ebae7a7db Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0012.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0013.sog b/examples/assets/splats/flipbook/monkey_0013.sog new file mode 100644 index 00000000000..0f60991cfb2 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0013.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0014.sog b/examples/assets/splats/flipbook/monkey_0014.sog new file mode 100644 index 00000000000..b107125ef82 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0014.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0015.sog b/examples/assets/splats/flipbook/monkey_0015.sog new file mode 100644 index 00000000000..8b3c838c10b Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0015.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0016.sog b/examples/assets/splats/flipbook/monkey_0016.sog new file mode 100644 index 00000000000..0166c9b17b7 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0016.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0017.sog b/examples/assets/splats/flipbook/monkey_0017.sog new file mode 100644 index 00000000000..edb460dddd4 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0017.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0018.sog b/examples/assets/splats/flipbook/monkey_0018.sog new file mode 100644 index 00000000000..eb42a00ab8e Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0018.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0019.sog b/examples/assets/splats/flipbook/monkey_0019.sog new file mode 100644 index 00000000000..5830f5c51f2 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0019.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0020.sog b/examples/assets/splats/flipbook/monkey_0020.sog new file mode 100644 index 00000000000..b8dcf318ba2 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0020.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0021.sog b/examples/assets/splats/flipbook/monkey_0021.sog new file mode 100644 index 00000000000..0d5d4407135 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0021.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0022.sog b/examples/assets/splats/flipbook/monkey_0022.sog new file mode 100644 index 00000000000..8c33c0b59c7 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0022.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0023.sog b/examples/assets/splats/flipbook/monkey_0023.sog new file mode 100644 index 00000000000..575fb57cb95 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0023.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0024.sog b/examples/assets/splats/flipbook/monkey_0024.sog new file mode 100644 index 00000000000..da0b523f4eb Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0024.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0025.sog b/examples/assets/splats/flipbook/monkey_0025.sog new file mode 100644 index 00000000000..1fa49d24bbd Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0025.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0026.sog b/examples/assets/splats/flipbook/monkey_0026.sog new file mode 100644 index 00000000000..61a3237b5dd Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0026.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0027.sog b/examples/assets/splats/flipbook/monkey_0027.sog new file mode 100644 index 00000000000..3927cc2d128 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0027.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0028.sog b/examples/assets/splats/flipbook/monkey_0028.sog new file mode 100644 index 00000000000..9cb221b3a33 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0028.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0029.sog b/examples/assets/splats/flipbook/monkey_0029.sog new file mode 100644 index 00000000000..9fd974ed5aa Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0029.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0030.sog b/examples/assets/splats/flipbook/monkey_0030.sog new file mode 100644 index 00000000000..617b98a3e56 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0030.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0031.sog b/examples/assets/splats/flipbook/monkey_0031.sog new file mode 100644 index 00000000000..21133474d52 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0031.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0032.sog b/examples/assets/splats/flipbook/monkey_0032.sog new file mode 100644 index 00000000000..9569b5f0bff Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0032.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0033.sog b/examples/assets/splats/flipbook/monkey_0033.sog new file mode 100644 index 00000000000..e5a3fbe5505 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0033.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0034.sog b/examples/assets/splats/flipbook/monkey_0034.sog new file mode 100644 index 00000000000..c5f07c24c98 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0034.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0035.sog b/examples/assets/splats/flipbook/monkey_0035.sog new file mode 100644 index 00000000000..1ec7a9c21e2 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0035.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0036.sog b/examples/assets/splats/flipbook/monkey_0036.sog new file mode 100644 index 00000000000..4ea1b9a833f Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0036.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0037.sog b/examples/assets/splats/flipbook/monkey_0037.sog new file mode 100644 index 00000000000..74515242558 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0037.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0038.sog b/examples/assets/splats/flipbook/monkey_0038.sog new file mode 100644 index 00000000000..b5bd6fe4ec4 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0038.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0039.sog b/examples/assets/splats/flipbook/monkey_0039.sog new file mode 100644 index 00000000000..568ecde0c5a Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0039.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0040.sog b/examples/assets/splats/flipbook/monkey_0040.sog new file mode 100644 index 00000000000..b57ff8a92e2 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0040.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0041.sog b/examples/assets/splats/flipbook/monkey_0041.sog new file mode 100644 index 00000000000..e9f8e0b2f84 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0041.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0042.sog b/examples/assets/splats/flipbook/monkey_0042.sog new file mode 100644 index 00000000000..a0c9f05e42f Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0042.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0043.sog b/examples/assets/splats/flipbook/monkey_0043.sog new file mode 100644 index 00000000000..d1ad80b9525 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0043.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0044.sog b/examples/assets/splats/flipbook/monkey_0044.sog new file mode 100644 index 00000000000..3b8be91083e Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0044.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0045.sog b/examples/assets/splats/flipbook/monkey_0045.sog new file mode 100644 index 00000000000..cf564f33fe2 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0045.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0046.sog b/examples/assets/splats/flipbook/monkey_0046.sog new file mode 100644 index 00000000000..17481e222a0 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0046.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0047.sog b/examples/assets/splats/flipbook/monkey_0047.sog new file mode 100644 index 00000000000..863fa75574d Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0047.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0048.sog b/examples/assets/splats/flipbook/monkey_0048.sog new file mode 100644 index 00000000000..c9e230cce93 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0048.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0049.sog b/examples/assets/splats/flipbook/monkey_0049.sog new file mode 100644 index 00000000000..ab96e722dea Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0049.sog differ diff --git a/examples/assets/splats/flipbook/monkey_0050.sog b/examples/assets/splats/flipbook/monkey_0050.sog new file mode 100644 index 00000000000..9bc4cd9f2e7 Binary files /dev/null and b/examples/assets/splats/flipbook/monkey_0050.sog differ diff --git a/examples/assets/splats/wave/wave_0001.sog b/examples/assets/splats/wave/wave_0001.sog new file mode 100644 index 00000000000..97bb54085c0 Binary files /dev/null and b/examples/assets/splats/wave/wave_0001.sog differ diff --git a/examples/assets/splats/wave/wave_0002.sog b/examples/assets/splats/wave/wave_0002.sog new file mode 100644 index 00000000000..1cff1b35678 Binary files /dev/null and b/examples/assets/splats/wave/wave_0002.sog differ diff --git a/examples/assets/splats/wave/wave_0003.sog b/examples/assets/splats/wave/wave_0003.sog new file mode 100644 index 00000000000..e871cfee834 Binary files /dev/null and b/examples/assets/splats/wave/wave_0003.sog differ diff --git a/examples/assets/splats/wave/wave_0004.sog b/examples/assets/splats/wave/wave_0004.sog new file mode 100644 index 00000000000..bf4564ec394 Binary files /dev/null and b/examples/assets/splats/wave/wave_0004.sog differ diff --git a/examples/assets/splats/wave/wave_0005.sog b/examples/assets/splats/wave/wave_0005.sog new file mode 100644 index 00000000000..66c666fd3f2 Binary files /dev/null and b/examples/assets/splats/wave/wave_0005.sog differ diff --git a/examples/assets/splats/wave/wave_0006.sog b/examples/assets/splats/wave/wave_0006.sog new file mode 100644 index 00000000000..b770c9af6f4 Binary files /dev/null and b/examples/assets/splats/wave/wave_0006.sog differ diff --git a/examples/assets/splats/wave/wave_0007.sog b/examples/assets/splats/wave/wave_0007.sog new file mode 100644 index 00000000000..aa6931fa037 Binary files /dev/null and b/examples/assets/splats/wave/wave_0007.sog differ diff --git a/examples/assets/splats/wave/wave_0008.sog b/examples/assets/splats/wave/wave_0008.sog new file mode 100644 index 00000000000..21bbad39440 Binary files /dev/null and b/examples/assets/splats/wave/wave_0008.sog differ diff --git a/examples/assets/splats/wave/wave_0009.sog b/examples/assets/splats/wave/wave_0009.sog new file mode 100644 index 00000000000..ed7b77f5a9d Binary files /dev/null and b/examples/assets/splats/wave/wave_0009.sog differ diff --git a/examples/assets/splats/wave/wave_0010.sog b/examples/assets/splats/wave/wave_0010.sog new file mode 100644 index 00000000000..d81ae722bf4 Binary files /dev/null and b/examples/assets/splats/wave/wave_0010.sog differ diff --git a/examples/assets/splats/wave/wave_0011.sog b/examples/assets/splats/wave/wave_0011.sog new file mode 100644 index 00000000000..1f800e5a0b1 Binary files /dev/null and b/examples/assets/splats/wave/wave_0011.sog differ diff --git a/examples/assets/splats/wave/wave_0012.sog b/examples/assets/splats/wave/wave_0012.sog new file mode 100644 index 00000000000..88bc4aa049f Binary files /dev/null and b/examples/assets/splats/wave/wave_0012.sog differ diff --git a/examples/assets/splats/wave/wave_0013.sog b/examples/assets/splats/wave/wave_0013.sog new file mode 100644 index 00000000000..e20929a611c Binary files /dev/null and b/examples/assets/splats/wave/wave_0013.sog differ diff --git a/examples/assets/splats/wave/wave_0014.sog b/examples/assets/splats/wave/wave_0014.sog new file mode 100644 index 00000000000..823d6e70307 Binary files /dev/null and b/examples/assets/splats/wave/wave_0014.sog differ diff --git a/examples/assets/splats/wave/wave_0015.sog b/examples/assets/splats/wave/wave_0015.sog new file mode 100644 index 00000000000..f775aaca7ed Binary files /dev/null and b/examples/assets/splats/wave/wave_0015.sog differ diff --git a/examples/assets/splats/wave/wave_0016.sog b/examples/assets/splats/wave/wave_0016.sog new file mode 100644 index 00000000000..5958ca99484 Binary files /dev/null and b/examples/assets/splats/wave/wave_0016.sog differ diff --git a/examples/assets/splats/wave/wave_0017.sog b/examples/assets/splats/wave/wave_0017.sog new file mode 100644 index 00000000000..ff101736e25 Binary files /dev/null and b/examples/assets/splats/wave/wave_0017.sog differ diff --git a/examples/assets/splats/wave/wave_0018.sog b/examples/assets/splats/wave/wave_0018.sog new file mode 100644 index 00000000000..3e923a42f25 Binary files /dev/null and b/examples/assets/splats/wave/wave_0018.sog differ diff --git a/examples/assets/splats/wave/wave_0019.sog b/examples/assets/splats/wave/wave_0019.sog new file mode 100644 index 00000000000..fc8beb04c57 Binary files /dev/null and b/examples/assets/splats/wave/wave_0019.sog differ diff --git a/examples/assets/splats/wave/wave_0020.sog b/examples/assets/splats/wave/wave_0020.sog new file mode 100644 index 00000000000..bc55fb7a57b Binary files /dev/null and b/examples/assets/splats/wave/wave_0020.sog differ diff --git a/examples/assets/splats/wave/wave_0021.sog b/examples/assets/splats/wave/wave_0021.sog new file mode 100644 index 00000000000..11e9e8b554f Binary files /dev/null and b/examples/assets/splats/wave/wave_0021.sog differ diff --git a/examples/assets/splats/wave/wave_0022.sog b/examples/assets/splats/wave/wave_0022.sog new file mode 100644 index 00000000000..6f2c2058926 Binary files /dev/null and b/examples/assets/splats/wave/wave_0022.sog differ diff --git a/examples/assets/splats/wave/wave_0023.sog b/examples/assets/splats/wave/wave_0023.sog new file mode 100644 index 00000000000..4b407a162b6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0023.sog differ diff --git a/examples/assets/splats/wave/wave_0024.sog b/examples/assets/splats/wave/wave_0024.sog new file mode 100644 index 00000000000..5c1053bb1d5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0024.sog differ diff --git a/examples/assets/splats/wave/wave_0025.sog b/examples/assets/splats/wave/wave_0025.sog new file mode 100644 index 00000000000..9c29b9d071c Binary files /dev/null and b/examples/assets/splats/wave/wave_0025.sog differ diff --git a/examples/assets/splats/wave/wave_0026.sog b/examples/assets/splats/wave/wave_0026.sog new file mode 100644 index 00000000000..643d9658e17 Binary files /dev/null and b/examples/assets/splats/wave/wave_0026.sog differ diff --git a/examples/assets/splats/wave/wave_0027.sog b/examples/assets/splats/wave/wave_0027.sog new file mode 100644 index 00000000000..7a00a4ae533 Binary files /dev/null and b/examples/assets/splats/wave/wave_0027.sog differ diff --git a/examples/assets/splats/wave/wave_0028.sog b/examples/assets/splats/wave/wave_0028.sog new file mode 100644 index 00000000000..e4eee1600f5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0028.sog differ diff --git a/examples/assets/splats/wave/wave_0029.sog b/examples/assets/splats/wave/wave_0029.sog new file mode 100644 index 00000000000..0d12f5f2172 Binary files /dev/null and b/examples/assets/splats/wave/wave_0029.sog differ diff --git a/examples/assets/splats/wave/wave_0030.sog b/examples/assets/splats/wave/wave_0030.sog new file mode 100644 index 00000000000..2a96fcda2f6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0030.sog differ diff --git a/examples/assets/splats/wave/wave_0031.sog b/examples/assets/splats/wave/wave_0031.sog new file mode 100644 index 00000000000..be8d9f3d28e Binary files /dev/null and b/examples/assets/splats/wave/wave_0031.sog differ diff --git a/examples/assets/splats/wave/wave_0032.sog b/examples/assets/splats/wave/wave_0032.sog new file mode 100644 index 00000000000..f33fb0957f3 Binary files /dev/null and b/examples/assets/splats/wave/wave_0032.sog differ diff --git a/examples/assets/splats/wave/wave_0033.sog b/examples/assets/splats/wave/wave_0033.sog new file mode 100644 index 00000000000..db35cd992f0 Binary files /dev/null and b/examples/assets/splats/wave/wave_0033.sog differ diff --git a/examples/assets/splats/wave/wave_0034.sog b/examples/assets/splats/wave/wave_0034.sog new file mode 100644 index 00000000000..771f490886b Binary files /dev/null and b/examples/assets/splats/wave/wave_0034.sog differ diff --git a/examples/assets/splats/wave/wave_0035.sog b/examples/assets/splats/wave/wave_0035.sog new file mode 100644 index 00000000000..d235dd63ad9 Binary files /dev/null and b/examples/assets/splats/wave/wave_0035.sog differ diff --git a/examples/assets/splats/wave/wave_0036.sog b/examples/assets/splats/wave/wave_0036.sog new file mode 100644 index 00000000000..e9b3c5e472f Binary files /dev/null and b/examples/assets/splats/wave/wave_0036.sog differ diff --git a/examples/assets/splats/wave/wave_0037.sog b/examples/assets/splats/wave/wave_0037.sog new file mode 100644 index 00000000000..18f7919f6d6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0037.sog differ diff --git a/examples/assets/splats/wave/wave_0038.sog b/examples/assets/splats/wave/wave_0038.sog new file mode 100644 index 00000000000..acbbba4e9ec Binary files /dev/null and b/examples/assets/splats/wave/wave_0038.sog differ diff --git a/examples/assets/splats/wave/wave_0039.sog b/examples/assets/splats/wave/wave_0039.sog new file mode 100644 index 00000000000..98c9376166d Binary files /dev/null and b/examples/assets/splats/wave/wave_0039.sog differ diff --git a/examples/assets/splats/wave/wave_0040.sog b/examples/assets/splats/wave/wave_0040.sog new file mode 100644 index 00000000000..c8032754f1d Binary files /dev/null and b/examples/assets/splats/wave/wave_0040.sog differ diff --git a/examples/assets/splats/wave/wave_0041.sog b/examples/assets/splats/wave/wave_0041.sog new file mode 100644 index 00000000000..515a2b22bd9 Binary files /dev/null and b/examples/assets/splats/wave/wave_0041.sog differ diff --git a/examples/assets/splats/wave/wave_0042.sog b/examples/assets/splats/wave/wave_0042.sog new file mode 100644 index 00000000000..60b55962736 Binary files /dev/null and b/examples/assets/splats/wave/wave_0042.sog differ diff --git a/examples/assets/splats/wave/wave_0043.sog b/examples/assets/splats/wave/wave_0043.sog new file mode 100644 index 00000000000..7befe5958e2 Binary files /dev/null and b/examples/assets/splats/wave/wave_0043.sog differ diff --git a/examples/assets/splats/wave/wave_0044.sog b/examples/assets/splats/wave/wave_0044.sog new file mode 100644 index 00000000000..1307857baeb Binary files /dev/null and b/examples/assets/splats/wave/wave_0044.sog differ diff --git a/examples/assets/splats/wave/wave_0045.sog b/examples/assets/splats/wave/wave_0045.sog new file mode 100644 index 00000000000..dbd2175af0f Binary files /dev/null and b/examples/assets/splats/wave/wave_0045.sog differ diff --git a/examples/assets/splats/wave/wave_0046.sog b/examples/assets/splats/wave/wave_0046.sog new file mode 100644 index 00000000000..0588a98ad96 Binary files /dev/null and b/examples/assets/splats/wave/wave_0046.sog differ diff --git a/examples/assets/splats/wave/wave_0047.sog b/examples/assets/splats/wave/wave_0047.sog new file mode 100644 index 00000000000..e06e59919b7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0047.sog differ diff --git a/examples/assets/splats/wave/wave_0048.sog b/examples/assets/splats/wave/wave_0048.sog new file mode 100644 index 00000000000..cfe2cc0369d Binary files /dev/null and b/examples/assets/splats/wave/wave_0048.sog differ diff --git a/examples/assets/splats/wave/wave_0049.sog b/examples/assets/splats/wave/wave_0049.sog new file mode 100644 index 00000000000..5ba643670cc Binary files /dev/null and b/examples/assets/splats/wave/wave_0049.sog differ diff --git a/examples/assets/splats/wave/wave_0050.sog b/examples/assets/splats/wave/wave_0050.sog new file mode 100644 index 00000000000..5340854cfb6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0050.sog differ diff --git a/examples/assets/splats/wave/wave_0051.sog b/examples/assets/splats/wave/wave_0051.sog new file mode 100644 index 00000000000..be1c29377f7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0051.sog differ diff --git a/examples/assets/splats/wave/wave_0052.sog b/examples/assets/splats/wave/wave_0052.sog new file mode 100644 index 00000000000..0f416332cba Binary files /dev/null and b/examples/assets/splats/wave/wave_0052.sog differ diff --git a/examples/assets/splats/wave/wave_0053.sog b/examples/assets/splats/wave/wave_0053.sog new file mode 100644 index 00000000000..438a547bbdf Binary files /dev/null and b/examples/assets/splats/wave/wave_0053.sog differ diff --git a/examples/assets/splats/wave/wave_0054.sog b/examples/assets/splats/wave/wave_0054.sog new file mode 100644 index 00000000000..184fe43098e Binary files /dev/null and b/examples/assets/splats/wave/wave_0054.sog differ diff --git a/examples/assets/splats/wave/wave_0055.sog b/examples/assets/splats/wave/wave_0055.sog new file mode 100644 index 00000000000..c4587623c5a Binary files /dev/null and b/examples/assets/splats/wave/wave_0055.sog differ diff --git a/examples/assets/splats/wave/wave_0056.sog b/examples/assets/splats/wave/wave_0056.sog new file mode 100644 index 00000000000..73536ad29e4 Binary files /dev/null and b/examples/assets/splats/wave/wave_0056.sog differ diff --git a/examples/assets/splats/wave/wave_0057.sog b/examples/assets/splats/wave/wave_0057.sog new file mode 100644 index 00000000000..1a963bd0605 Binary files /dev/null and b/examples/assets/splats/wave/wave_0057.sog differ diff --git a/examples/assets/splats/wave/wave_0058.sog b/examples/assets/splats/wave/wave_0058.sog new file mode 100644 index 00000000000..ea132bb9963 Binary files /dev/null and b/examples/assets/splats/wave/wave_0058.sog differ diff --git a/examples/assets/splats/wave/wave_0059.sog b/examples/assets/splats/wave/wave_0059.sog new file mode 100644 index 00000000000..1eb739c43ae Binary files /dev/null and b/examples/assets/splats/wave/wave_0059.sog differ diff --git a/examples/assets/splats/wave/wave_0060.sog b/examples/assets/splats/wave/wave_0060.sog new file mode 100644 index 00000000000..02570bcc87b Binary files /dev/null and b/examples/assets/splats/wave/wave_0060.sog differ diff --git a/examples/assets/splats/wave/wave_0061.sog b/examples/assets/splats/wave/wave_0061.sog new file mode 100644 index 00000000000..da1cc03e75a Binary files /dev/null and b/examples/assets/splats/wave/wave_0061.sog differ diff --git a/examples/assets/splats/wave/wave_0062.sog b/examples/assets/splats/wave/wave_0062.sog new file mode 100644 index 00000000000..b73bd689dbe Binary files /dev/null and b/examples/assets/splats/wave/wave_0062.sog differ diff --git a/examples/assets/splats/wave/wave_0063.sog b/examples/assets/splats/wave/wave_0063.sog new file mode 100644 index 00000000000..e6b62c3d5b6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0063.sog differ diff --git a/examples/assets/splats/wave/wave_0064.sog b/examples/assets/splats/wave/wave_0064.sog new file mode 100644 index 00000000000..14f6af17026 Binary files /dev/null and b/examples/assets/splats/wave/wave_0064.sog differ diff --git a/examples/assets/splats/wave/wave_0065.sog b/examples/assets/splats/wave/wave_0065.sog new file mode 100644 index 00000000000..491645efd99 Binary files /dev/null and b/examples/assets/splats/wave/wave_0065.sog differ diff --git a/examples/assets/splats/wave/wave_0066.sog b/examples/assets/splats/wave/wave_0066.sog new file mode 100644 index 00000000000..3ee469ed8c4 Binary files /dev/null and b/examples/assets/splats/wave/wave_0066.sog differ diff --git a/examples/assets/splats/wave/wave_0067.sog b/examples/assets/splats/wave/wave_0067.sog new file mode 100644 index 00000000000..18cbb99617b Binary files /dev/null and b/examples/assets/splats/wave/wave_0067.sog differ diff --git a/examples/assets/splats/wave/wave_0068.sog b/examples/assets/splats/wave/wave_0068.sog new file mode 100644 index 00000000000..d377d32c264 Binary files /dev/null and b/examples/assets/splats/wave/wave_0068.sog differ diff --git a/examples/assets/splats/wave/wave_0069.sog b/examples/assets/splats/wave/wave_0069.sog new file mode 100644 index 00000000000..95c5ed34d9b Binary files /dev/null and b/examples/assets/splats/wave/wave_0069.sog differ diff --git a/examples/assets/splats/wave/wave_0070.sog b/examples/assets/splats/wave/wave_0070.sog new file mode 100644 index 00000000000..de914e4b957 Binary files /dev/null and b/examples/assets/splats/wave/wave_0070.sog differ diff --git a/examples/assets/splats/wave/wave_0071.sog b/examples/assets/splats/wave/wave_0071.sog new file mode 100644 index 00000000000..def8af883c7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0071.sog differ diff --git a/examples/assets/splats/wave/wave_0072.sog b/examples/assets/splats/wave/wave_0072.sog new file mode 100644 index 00000000000..e1b921223e2 Binary files /dev/null and b/examples/assets/splats/wave/wave_0072.sog differ diff --git a/examples/assets/splats/wave/wave_0073.sog b/examples/assets/splats/wave/wave_0073.sog new file mode 100644 index 00000000000..aa5375f3afd Binary files /dev/null and b/examples/assets/splats/wave/wave_0073.sog differ diff --git a/examples/assets/splats/wave/wave_0074.sog b/examples/assets/splats/wave/wave_0074.sog new file mode 100644 index 00000000000..52a91a89de3 Binary files /dev/null and b/examples/assets/splats/wave/wave_0074.sog differ diff --git a/examples/assets/splats/wave/wave_0075.sog b/examples/assets/splats/wave/wave_0075.sog new file mode 100644 index 00000000000..95a0ff928bb Binary files /dev/null and b/examples/assets/splats/wave/wave_0075.sog differ diff --git a/examples/assets/splats/wave/wave_0076.sog b/examples/assets/splats/wave/wave_0076.sog new file mode 100644 index 00000000000..af469110cbc Binary files /dev/null and b/examples/assets/splats/wave/wave_0076.sog differ diff --git a/examples/assets/splats/wave/wave_0077.sog b/examples/assets/splats/wave/wave_0077.sog new file mode 100644 index 00000000000..c988b43fa79 Binary files /dev/null and b/examples/assets/splats/wave/wave_0077.sog differ diff --git a/examples/assets/splats/wave/wave_0078.sog b/examples/assets/splats/wave/wave_0078.sog new file mode 100644 index 00000000000..d11334b2e81 Binary files /dev/null and b/examples/assets/splats/wave/wave_0078.sog differ diff --git a/examples/assets/splats/wave/wave_0079.sog b/examples/assets/splats/wave/wave_0079.sog new file mode 100644 index 00000000000..c28a018c505 Binary files /dev/null and b/examples/assets/splats/wave/wave_0079.sog differ diff --git a/examples/assets/splats/wave/wave_0080.sog b/examples/assets/splats/wave/wave_0080.sog new file mode 100644 index 00000000000..54838b35a80 Binary files /dev/null and b/examples/assets/splats/wave/wave_0080.sog differ diff --git a/examples/assets/splats/wave/wave_0081.sog b/examples/assets/splats/wave/wave_0081.sog new file mode 100644 index 00000000000..630ec5b4124 Binary files /dev/null and b/examples/assets/splats/wave/wave_0081.sog differ diff --git a/examples/assets/splats/wave/wave_0082.sog b/examples/assets/splats/wave/wave_0082.sog new file mode 100644 index 00000000000..22486afcae6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0082.sog differ diff --git a/examples/assets/splats/wave/wave_0083.sog b/examples/assets/splats/wave/wave_0083.sog new file mode 100644 index 00000000000..082eab5db01 Binary files /dev/null and b/examples/assets/splats/wave/wave_0083.sog differ diff --git a/examples/assets/splats/wave/wave_0084.sog b/examples/assets/splats/wave/wave_0084.sog new file mode 100644 index 00000000000..cff14fbff84 Binary files /dev/null and b/examples/assets/splats/wave/wave_0084.sog differ diff --git a/examples/assets/splats/wave/wave_0085.sog b/examples/assets/splats/wave/wave_0085.sog new file mode 100644 index 00000000000..3218f3bf900 Binary files /dev/null and b/examples/assets/splats/wave/wave_0085.sog differ diff --git a/examples/assets/splats/wave/wave_0086.sog b/examples/assets/splats/wave/wave_0086.sog new file mode 100644 index 00000000000..da9fe9c4875 Binary files /dev/null and b/examples/assets/splats/wave/wave_0086.sog differ diff --git a/examples/assets/splats/wave/wave_0087.sog b/examples/assets/splats/wave/wave_0087.sog new file mode 100644 index 00000000000..e76518d1172 Binary files /dev/null and b/examples/assets/splats/wave/wave_0087.sog differ diff --git a/examples/assets/splats/wave/wave_0088.sog b/examples/assets/splats/wave/wave_0088.sog new file mode 100644 index 00000000000..6b54d5370cd Binary files /dev/null and b/examples/assets/splats/wave/wave_0088.sog differ diff --git a/examples/assets/splats/wave/wave_0089.sog b/examples/assets/splats/wave/wave_0089.sog new file mode 100644 index 00000000000..7bf58bf9e3c Binary files /dev/null and b/examples/assets/splats/wave/wave_0089.sog differ diff --git a/examples/assets/splats/wave/wave_0090.sog b/examples/assets/splats/wave/wave_0090.sog new file mode 100644 index 00000000000..8faab636525 Binary files /dev/null and b/examples/assets/splats/wave/wave_0090.sog differ diff --git a/examples/assets/splats/wave/wave_0091.sog b/examples/assets/splats/wave/wave_0091.sog new file mode 100644 index 00000000000..9945b2dc6a4 Binary files /dev/null and b/examples/assets/splats/wave/wave_0091.sog differ diff --git a/examples/assets/splats/wave/wave_0092.sog b/examples/assets/splats/wave/wave_0092.sog new file mode 100644 index 00000000000..56f0e52aff7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0092.sog differ diff --git a/examples/assets/splats/wave/wave_0093.sog b/examples/assets/splats/wave/wave_0093.sog new file mode 100644 index 00000000000..3f1225f0ebe Binary files /dev/null and b/examples/assets/splats/wave/wave_0093.sog differ diff --git a/examples/assets/splats/wave/wave_0094.sog b/examples/assets/splats/wave/wave_0094.sog new file mode 100644 index 00000000000..f9369af586b Binary files /dev/null and b/examples/assets/splats/wave/wave_0094.sog differ diff --git a/examples/assets/splats/wave/wave_0095.sog b/examples/assets/splats/wave/wave_0095.sog new file mode 100644 index 00000000000..0af0d6496f1 Binary files /dev/null and b/examples/assets/splats/wave/wave_0095.sog differ diff --git a/examples/assets/splats/wave/wave_0096.sog b/examples/assets/splats/wave/wave_0096.sog new file mode 100644 index 00000000000..40ea5a492c5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0096.sog differ diff --git a/examples/assets/splats/wave/wave_0097.sog b/examples/assets/splats/wave/wave_0097.sog new file mode 100644 index 00000000000..88c4214cd74 Binary files /dev/null and b/examples/assets/splats/wave/wave_0097.sog differ diff --git a/examples/assets/splats/wave/wave_0098.sog b/examples/assets/splats/wave/wave_0098.sog new file mode 100644 index 00000000000..c97a01e69f2 Binary files /dev/null and b/examples/assets/splats/wave/wave_0098.sog differ diff --git a/examples/assets/splats/wave/wave_0099.sog b/examples/assets/splats/wave/wave_0099.sog new file mode 100644 index 00000000000..a536a1c42c9 Binary files /dev/null and b/examples/assets/splats/wave/wave_0099.sog differ diff --git a/examples/assets/splats/wave/wave_0100.sog b/examples/assets/splats/wave/wave_0100.sog new file mode 100644 index 00000000000..e3bf2e3e946 Binary files /dev/null and b/examples/assets/splats/wave/wave_0100.sog differ diff --git a/examples/assets/splats/wave/wave_0101.sog b/examples/assets/splats/wave/wave_0101.sog new file mode 100644 index 00000000000..8d594f353cd Binary files /dev/null and b/examples/assets/splats/wave/wave_0101.sog differ diff --git a/examples/assets/splats/wave/wave_0102.sog b/examples/assets/splats/wave/wave_0102.sog new file mode 100644 index 00000000000..99155e7ca79 Binary files /dev/null and b/examples/assets/splats/wave/wave_0102.sog differ diff --git a/examples/assets/splats/wave/wave_0103.sog b/examples/assets/splats/wave/wave_0103.sog new file mode 100644 index 00000000000..de5135c8a95 Binary files /dev/null and b/examples/assets/splats/wave/wave_0103.sog differ diff --git a/examples/assets/splats/wave/wave_0104.sog b/examples/assets/splats/wave/wave_0104.sog new file mode 100644 index 00000000000..171b513bce8 Binary files /dev/null and b/examples/assets/splats/wave/wave_0104.sog differ diff --git a/examples/assets/splats/wave/wave_0105.sog b/examples/assets/splats/wave/wave_0105.sog new file mode 100644 index 00000000000..b2e6a499456 Binary files /dev/null and b/examples/assets/splats/wave/wave_0105.sog differ diff --git a/examples/assets/splats/wave/wave_0106.sog b/examples/assets/splats/wave/wave_0106.sog new file mode 100644 index 00000000000..e619af25960 Binary files /dev/null and b/examples/assets/splats/wave/wave_0106.sog differ diff --git a/examples/assets/splats/wave/wave_0107.sog b/examples/assets/splats/wave/wave_0107.sog new file mode 100644 index 00000000000..6d7d753bb71 Binary files /dev/null and b/examples/assets/splats/wave/wave_0107.sog differ diff --git a/examples/assets/splats/wave/wave_0108.sog b/examples/assets/splats/wave/wave_0108.sog new file mode 100644 index 00000000000..37f78353cca Binary files /dev/null and b/examples/assets/splats/wave/wave_0108.sog differ diff --git a/examples/assets/splats/wave/wave_0109.sog b/examples/assets/splats/wave/wave_0109.sog new file mode 100644 index 00000000000..c75f57acf49 Binary files /dev/null and b/examples/assets/splats/wave/wave_0109.sog differ diff --git a/examples/assets/splats/wave/wave_0110.sog b/examples/assets/splats/wave/wave_0110.sog new file mode 100644 index 00000000000..fca9884b943 Binary files /dev/null and b/examples/assets/splats/wave/wave_0110.sog differ diff --git a/examples/assets/splats/wave/wave_0111.sog b/examples/assets/splats/wave/wave_0111.sog new file mode 100644 index 00000000000..23ea40876fe Binary files /dev/null and b/examples/assets/splats/wave/wave_0111.sog differ diff --git a/examples/assets/splats/wave/wave_0112.sog b/examples/assets/splats/wave/wave_0112.sog new file mode 100644 index 00000000000..8d4f9dc35ce Binary files /dev/null and b/examples/assets/splats/wave/wave_0112.sog differ diff --git a/examples/assets/splats/wave/wave_0113.sog b/examples/assets/splats/wave/wave_0113.sog new file mode 100644 index 00000000000..7546bcb5d73 Binary files /dev/null and b/examples/assets/splats/wave/wave_0113.sog differ diff --git a/examples/assets/splats/wave/wave_0114.sog b/examples/assets/splats/wave/wave_0114.sog new file mode 100644 index 00000000000..2946fb88b54 Binary files /dev/null and b/examples/assets/splats/wave/wave_0114.sog differ diff --git a/examples/assets/splats/wave/wave_0115.sog b/examples/assets/splats/wave/wave_0115.sog new file mode 100644 index 00000000000..ad77f724de7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0115.sog differ diff --git a/examples/assets/splats/wave/wave_0116.sog b/examples/assets/splats/wave/wave_0116.sog new file mode 100644 index 00000000000..189e30883ec Binary files /dev/null and b/examples/assets/splats/wave/wave_0116.sog differ diff --git a/examples/assets/splats/wave/wave_0117.sog b/examples/assets/splats/wave/wave_0117.sog new file mode 100644 index 00000000000..4f9e0559525 Binary files /dev/null and b/examples/assets/splats/wave/wave_0117.sog differ diff --git a/examples/assets/splats/wave/wave_0118.sog b/examples/assets/splats/wave/wave_0118.sog new file mode 100644 index 00000000000..f5e5cdb3938 Binary files /dev/null and b/examples/assets/splats/wave/wave_0118.sog differ diff --git a/examples/assets/splats/wave/wave_0119.sog b/examples/assets/splats/wave/wave_0119.sog new file mode 100644 index 00000000000..f4ae2cf132b Binary files /dev/null and b/examples/assets/splats/wave/wave_0119.sog differ diff --git a/examples/assets/splats/wave/wave_0120.sog b/examples/assets/splats/wave/wave_0120.sog new file mode 100644 index 00000000000..8f00f96ae2a Binary files /dev/null and b/examples/assets/splats/wave/wave_0120.sog differ diff --git a/examples/assets/splats/wave/wave_0121.sog b/examples/assets/splats/wave/wave_0121.sog new file mode 100644 index 00000000000..cb8bba16423 Binary files /dev/null and b/examples/assets/splats/wave/wave_0121.sog differ diff --git a/examples/assets/splats/wave/wave_0122.sog b/examples/assets/splats/wave/wave_0122.sog new file mode 100644 index 00000000000..12d014f5686 Binary files /dev/null and b/examples/assets/splats/wave/wave_0122.sog differ diff --git a/examples/assets/splats/wave/wave_0123.sog b/examples/assets/splats/wave/wave_0123.sog new file mode 100644 index 00000000000..e80e6695d63 Binary files /dev/null and b/examples/assets/splats/wave/wave_0123.sog differ diff --git a/examples/assets/splats/wave/wave_0124.sog b/examples/assets/splats/wave/wave_0124.sog new file mode 100644 index 00000000000..8f32f834111 Binary files /dev/null and b/examples/assets/splats/wave/wave_0124.sog differ diff --git a/examples/assets/splats/wave/wave_0125.sog b/examples/assets/splats/wave/wave_0125.sog new file mode 100644 index 00000000000..8756fae43b5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0125.sog differ diff --git a/examples/assets/splats/wave/wave_0126.sog b/examples/assets/splats/wave/wave_0126.sog new file mode 100644 index 00000000000..4802b1aba1f Binary files /dev/null and b/examples/assets/splats/wave/wave_0126.sog differ diff --git a/examples/assets/splats/wave/wave_0127.sog b/examples/assets/splats/wave/wave_0127.sog new file mode 100644 index 00000000000..890bc40fb8c Binary files /dev/null and b/examples/assets/splats/wave/wave_0127.sog differ diff --git a/examples/assets/splats/wave/wave_0128.sog b/examples/assets/splats/wave/wave_0128.sog new file mode 100644 index 00000000000..4af31a974c7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0128.sog differ diff --git a/examples/assets/splats/wave/wave_0129.sog b/examples/assets/splats/wave/wave_0129.sog new file mode 100644 index 00000000000..9cc193c6fd8 Binary files /dev/null and b/examples/assets/splats/wave/wave_0129.sog differ diff --git a/examples/assets/splats/wave/wave_0130.sog b/examples/assets/splats/wave/wave_0130.sog new file mode 100644 index 00000000000..99a2a5d8677 Binary files /dev/null and b/examples/assets/splats/wave/wave_0130.sog differ diff --git a/examples/assets/splats/wave/wave_0131.sog b/examples/assets/splats/wave/wave_0131.sog new file mode 100644 index 00000000000..b4698ef17d4 Binary files /dev/null and b/examples/assets/splats/wave/wave_0131.sog differ diff --git a/examples/assets/splats/wave/wave_0132.sog b/examples/assets/splats/wave/wave_0132.sog new file mode 100644 index 00000000000..993a4b9ae72 Binary files /dev/null and b/examples/assets/splats/wave/wave_0132.sog differ diff --git a/examples/assets/splats/wave/wave_0133.sog b/examples/assets/splats/wave/wave_0133.sog new file mode 100644 index 00000000000..f701ae3714c Binary files /dev/null and b/examples/assets/splats/wave/wave_0133.sog differ diff --git a/examples/assets/splats/wave/wave_0134.sog b/examples/assets/splats/wave/wave_0134.sog new file mode 100644 index 00000000000..c2b5c8492b9 Binary files /dev/null and b/examples/assets/splats/wave/wave_0134.sog differ diff --git a/examples/assets/splats/wave/wave_0135.sog b/examples/assets/splats/wave/wave_0135.sog new file mode 100644 index 00000000000..a66a8021d4f Binary files /dev/null and b/examples/assets/splats/wave/wave_0135.sog differ diff --git a/examples/assets/splats/wave/wave_0136.sog b/examples/assets/splats/wave/wave_0136.sog new file mode 100644 index 00000000000..2f1c7888284 Binary files /dev/null and b/examples/assets/splats/wave/wave_0136.sog differ diff --git a/examples/assets/splats/wave/wave_0137.sog b/examples/assets/splats/wave/wave_0137.sog new file mode 100644 index 00000000000..394004772af Binary files /dev/null and b/examples/assets/splats/wave/wave_0137.sog differ diff --git a/examples/assets/splats/wave/wave_0138.sog b/examples/assets/splats/wave/wave_0138.sog new file mode 100644 index 00000000000..e62ca17c192 Binary files /dev/null and b/examples/assets/splats/wave/wave_0138.sog differ diff --git a/examples/assets/splats/wave/wave_0139.sog b/examples/assets/splats/wave/wave_0139.sog new file mode 100644 index 00000000000..404a9080951 Binary files /dev/null and b/examples/assets/splats/wave/wave_0139.sog differ diff --git a/examples/assets/splats/wave/wave_0140.sog b/examples/assets/splats/wave/wave_0140.sog new file mode 100644 index 00000000000..01741b78dec Binary files /dev/null and b/examples/assets/splats/wave/wave_0140.sog differ diff --git a/examples/assets/splats/wave/wave_0141.sog b/examples/assets/splats/wave/wave_0141.sog new file mode 100644 index 00000000000..60de7ec9df1 Binary files /dev/null and b/examples/assets/splats/wave/wave_0141.sog differ diff --git a/examples/assets/splats/wave/wave_0142.sog b/examples/assets/splats/wave/wave_0142.sog new file mode 100644 index 00000000000..e2aab725e94 Binary files /dev/null and b/examples/assets/splats/wave/wave_0142.sog differ diff --git a/examples/assets/splats/wave/wave_0143.sog b/examples/assets/splats/wave/wave_0143.sog new file mode 100644 index 00000000000..de661f50958 Binary files /dev/null and b/examples/assets/splats/wave/wave_0143.sog differ diff --git a/examples/assets/splats/wave/wave_0144.sog b/examples/assets/splats/wave/wave_0144.sog new file mode 100644 index 00000000000..392fe47b28e Binary files /dev/null and b/examples/assets/splats/wave/wave_0144.sog differ diff --git a/examples/assets/splats/wave/wave_0145.sog b/examples/assets/splats/wave/wave_0145.sog new file mode 100644 index 00000000000..2aad10d4e77 Binary files /dev/null and b/examples/assets/splats/wave/wave_0145.sog differ diff --git a/examples/assets/splats/wave/wave_0146.sog b/examples/assets/splats/wave/wave_0146.sog new file mode 100644 index 00000000000..f147eb86442 Binary files /dev/null and b/examples/assets/splats/wave/wave_0146.sog differ diff --git a/examples/assets/splats/wave/wave_0147.sog b/examples/assets/splats/wave/wave_0147.sog new file mode 100644 index 00000000000..a33f34610fb Binary files /dev/null and b/examples/assets/splats/wave/wave_0147.sog differ diff --git a/examples/assets/splats/wave/wave_0148.sog b/examples/assets/splats/wave/wave_0148.sog new file mode 100644 index 00000000000..60c19d297a3 Binary files /dev/null and b/examples/assets/splats/wave/wave_0148.sog differ diff --git a/examples/assets/splats/wave/wave_0149.sog b/examples/assets/splats/wave/wave_0149.sog new file mode 100644 index 00000000000..42212381b58 Binary files /dev/null and b/examples/assets/splats/wave/wave_0149.sog differ diff --git a/examples/assets/splats/wave/wave_0150.sog b/examples/assets/splats/wave/wave_0150.sog new file mode 100644 index 00000000000..403b4696a22 Binary files /dev/null and b/examples/assets/splats/wave/wave_0150.sog differ diff --git a/examples/assets/splats/wave/wave_0151.sog b/examples/assets/splats/wave/wave_0151.sog new file mode 100644 index 00000000000..345b1fc90c6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0151.sog differ diff --git a/examples/assets/splats/wave/wave_0152.sog b/examples/assets/splats/wave/wave_0152.sog new file mode 100644 index 00000000000..620414a1931 Binary files /dev/null and b/examples/assets/splats/wave/wave_0152.sog differ diff --git a/examples/assets/splats/wave/wave_0153.sog b/examples/assets/splats/wave/wave_0153.sog new file mode 100644 index 00000000000..4475e54efaa Binary files /dev/null and b/examples/assets/splats/wave/wave_0153.sog differ diff --git a/examples/assets/splats/wave/wave_0154.sog b/examples/assets/splats/wave/wave_0154.sog new file mode 100644 index 00000000000..f6642f233e9 Binary files /dev/null and b/examples/assets/splats/wave/wave_0154.sog differ diff --git a/examples/assets/splats/wave/wave_0155.sog b/examples/assets/splats/wave/wave_0155.sog new file mode 100644 index 00000000000..25d13504c0a Binary files /dev/null and b/examples/assets/splats/wave/wave_0155.sog differ diff --git a/examples/assets/splats/wave/wave_0156.sog b/examples/assets/splats/wave/wave_0156.sog new file mode 100644 index 00000000000..27f24790705 Binary files /dev/null and b/examples/assets/splats/wave/wave_0156.sog differ diff --git a/examples/assets/splats/wave/wave_0157.sog b/examples/assets/splats/wave/wave_0157.sog new file mode 100644 index 00000000000..9bec972dad7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0157.sog differ diff --git a/examples/assets/splats/wave/wave_0158.sog b/examples/assets/splats/wave/wave_0158.sog new file mode 100644 index 00000000000..714379379b3 Binary files /dev/null and b/examples/assets/splats/wave/wave_0158.sog differ diff --git a/examples/assets/splats/wave/wave_0159.sog b/examples/assets/splats/wave/wave_0159.sog new file mode 100644 index 00000000000..0f68a36c5f0 Binary files /dev/null and b/examples/assets/splats/wave/wave_0159.sog differ diff --git a/examples/assets/splats/wave/wave_0160.sog b/examples/assets/splats/wave/wave_0160.sog new file mode 100644 index 00000000000..1d5122b1333 Binary files /dev/null and b/examples/assets/splats/wave/wave_0160.sog differ diff --git a/examples/assets/splats/wave/wave_0161.sog b/examples/assets/splats/wave/wave_0161.sog new file mode 100644 index 00000000000..ce4976ad802 Binary files /dev/null and b/examples/assets/splats/wave/wave_0161.sog differ diff --git a/examples/assets/splats/wave/wave_0162.sog b/examples/assets/splats/wave/wave_0162.sog new file mode 100644 index 00000000000..b9470485028 Binary files /dev/null and b/examples/assets/splats/wave/wave_0162.sog differ diff --git a/examples/assets/splats/wave/wave_0163.sog b/examples/assets/splats/wave/wave_0163.sog new file mode 100644 index 00000000000..8f93271bb8e Binary files /dev/null and b/examples/assets/splats/wave/wave_0163.sog differ diff --git a/examples/assets/splats/wave/wave_0164.sog b/examples/assets/splats/wave/wave_0164.sog new file mode 100644 index 00000000000..2bdde5b8314 Binary files /dev/null and b/examples/assets/splats/wave/wave_0164.sog differ diff --git a/examples/assets/splats/wave/wave_0165.sog b/examples/assets/splats/wave/wave_0165.sog new file mode 100644 index 00000000000..e383d2019f5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0165.sog differ diff --git a/examples/assets/splats/wave/wave_0166.sog b/examples/assets/splats/wave/wave_0166.sog new file mode 100644 index 00000000000..9b7b75aa818 Binary files /dev/null and b/examples/assets/splats/wave/wave_0166.sog differ diff --git a/examples/assets/splats/wave/wave_0167.sog b/examples/assets/splats/wave/wave_0167.sog new file mode 100644 index 00000000000..03344a05ef5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0167.sog differ diff --git a/examples/assets/splats/wave/wave_0168.sog b/examples/assets/splats/wave/wave_0168.sog new file mode 100644 index 00000000000..94018b97886 Binary files /dev/null and b/examples/assets/splats/wave/wave_0168.sog differ diff --git a/examples/assets/splats/wave/wave_0169.sog b/examples/assets/splats/wave/wave_0169.sog new file mode 100644 index 00000000000..dd9a69add07 Binary files /dev/null and b/examples/assets/splats/wave/wave_0169.sog differ diff --git a/examples/assets/splats/wave/wave_0170.sog b/examples/assets/splats/wave/wave_0170.sog new file mode 100644 index 00000000000..a05ede2cbef Binary files /dev/null and b/examples/assets/splats/wave/wave_0170.sog differ diff --git a/examples/assets/splats/wave/wave_0171.sog b/examples/assets/splats/wave/wave_0171.sog new file mode 100644 index 00000000000..cfce60e8acb Binary files /dev/null and b/examples/assets/splats/wave/wave_0171.sog differ diff --git a/examples/assets/splats/wave/wave_0172.sog b/examples/assets/splats/wave/wave_0172.sog new file mode 100644 index 00000000000..dd0b571dd16 Binary files /dev/null and b/examples/assets/splats/wave/wave_0172.sog differ diff --git a/examples/assets/splats/wave/wave_0173.sog b/examples/assets/splats/wave/wave_0173.sog new file mode 100644 index 00000000000..9c5dc018311 Binary files /dev/null and b/examples/assets/splats/wave/wave_0173.sog differ diff --git a/examples/assets/splats/wave/wave_0174.sog b/examples/assets/splats/wave/wave_0174.sog new file mode 100644 index 00000000000..75da1df0950 Binary files /dev/null and b/examples/assets/splats/wave/wave_0174.sog differ diff --git a/examples/assets/splats/wave/wave_0175.sog b/examples/assets/splats/wave/wave_0175.sog new file mode 100644 index 00000000000..63212fc9336 Binary files /dev/null and b/examples/assets/splats/wave/wave_0175.sog differ diff --git a/examples/assets/splats/wave/wave_0176.sog b/examples/assets/splats/wave/wave_0176.sog new file mode 100644 index 00000000000..c716bcdc376 Binary files /dev/null and b/examples/assets/splats/wave/wave_0176.sog differ diff --git a/examples/assets/splats/wave/wave_0177.sog b/examples/assets/splats/wave/wave_0177.sog new file mode 100644 index 00000000000..6a8e96a6aaa Binary files /dev/null and b/examples/assets/splats/wave/wave_0177.sog differ diff --git a/examples/assets/splats/wave/wave_0178.sog b/examples/assets/splats/wave/wave_0178.sog new file mode 100644 index 00000000000..932991e1ac6 Binary files /dev/null and b/examples/assets/splats/wave/wave_0178.sog differ diff --git a/examples/assets/splats/wave/wave_0179.sog b/examples/assets/splats/wave/wave_0179.sog new file mode 100644 index 00000000000..784d40088d0 Binary files /dev/null and b/examples/assets/splats/wave/wave_0179.sog differ diff --git a/examples/assets/splats/wave/wave_0180.sog b/examples/assets/splats/wave/wave_0180.sog new file mode 100644 index 00000000000..f268dab9590 Binary files /dev/null and b/examples/assets/splats/wave/wave_0180.sog differ diff --git a/examples/assets/splats/wave/wave_0181.sog b/examples/assets/splats/wave/wave_0181.sog new file mode 100644 index 00000000000..bdede040201 Binary files /dev/null and b/examples/assets/splats/wave/wave_0181.sog differ diff --git a/examples/assets/splats/wave/wave_0182.sog b/examples/assets/splats/wave/wave_0182.sog new file mode 100644 index 00000000000..7bde3b1cf06 Binary files /dev/null and b/examples/assets/splats/wave/wave_0182.sog differ diff --git a/examples/assets/splats/wave/wave_0183.sog b/examples/assets/splats/wave/wave_0183.sog new file mode 100644 index 00000000000..63563e834c2 Binary files /dev/null and b/examples/assets/splats/wave/wave_0183.sog differ diff --git a/examples/assets/splats/wave/wave_0184.sog b/examples/assets/splats/wave/wave_0184.sog new file mode 100644 index 00000000000..f358737a4fa Binary files /dev/null and b/examples/assets/splats/wave/wave_0184.sog differ diff --git a/examples/assets/splats/wave/wave_0185.sog b/examples/assets/splats/wave/wave_0185.sog new file mode 100644 index 00000000000..96facbec6c5 Binary files /dev/null and b/examples/assets/splats/wave/wave_0185.sog differ diff --git a/examples/assets/splats/wave/wave_0186.sog b/examples/assets/splats/wave/wave_0186.sog new file mode 100644 index 00000000000..3e36452ff4c Binary files /dev/null and b/examples/assets/splats/wave/wave_0186.sog differ diff --git a/examples/assets/splats/wave/wave_0187.sog b/examples/assets/splats/wave/wave_0187.sog new file mode 100644 index 00000000000..2382d5f1f9e Binary files /dev/null and b/examples/assets/splats/wave/wave_0187.sog differ diff --git a/examples/assets/splats/wave/wave_0188.sog b/examples/assets/splats/wave/wave_0188.sog new file mode 100644 index 00000000000..70534bbf832 Binary files /dev/null and b/examples/assets/splats/wave/wave_0188.sog differ diff --git a/examples/assets/splats/wave/wave_0189.sog b/examples/assets/splats/wave/wave_0189.sog new file mode 100644 index 00000000000..34fc9f68a82 Binary files /dev/null and b/examples/assets/splats/wave/wave_0189.sog differ diff --git a/examples/assets/splats/wave/wave_0190.sog b/examples/assets/splats/wave/wave_0190.sog new file mode 100644 index 00000000000..0e4218d443e Binary files /dev/null and b/examples/assets/splats/wave/wave_0190.sog differ diff --git a/examples/assets/splats/wave/wave_0191.sog b/examples/assets/splats/wave/wave_0191.sog new file mode 100644 index 00000000000..e00551807e7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0191.sog differ diff --git a/examples/assets/splats/wave/wave_0192.sog b/examples/assets/splats/wave/wave_0192.sog new file mode 100644 index 00000000000..964f79d73d1 Binary files /dev/null and b/examples/assets/splats/wave/wave_0192.sog differ diff --git a/examples/assets/splats/wave/wave_0193.sog b/examples/assets/splats/wave/wave_0193.sog new file mode 100644 index 00000000000..cf3e903d3bb Binary files /dev/null and b/examples/assets/splats/wave/wave_0193.sog differ diff --git a/examples/assets/splats/wave/wave_0194.sog b/examples/assets/splats/wave/wave_0194.sog new file mode 100644 index 00000000000..63e40249631 Binary files /dev/null and b/examples/assets/splats/wave/wave_0194.sog differ diff --git a/examples/assets/splats/wave/wave_0195.sog b/examples/assets/splats/wave/wave_0195.sog new file mode 100644 index 00000000000..b9cff9e0d1c Binary files /dev/null and b/examples/assets/splats/wave/wave_0195.sog differ diff --git a/examples/assets/splats/wave/wave_0196.sog b/examples/assets/splats/wave/wave_0196.sog new file mode 100644 index 00000000000..c7e1d1f3ea2 Binary files /dev/null and b/examples/assets/splats/wave/wave_0196.sog differ diff --git a/examples/assets/splats/wave/wave_0197.sog b/examples/assets/splats/wave/wave_0197.sog new file mode 100644 index 00000000000..6e9ab3a5f52 Binary files /dev/null and b/examples/assets/splats/wave/wave_0197.sog differ diff --git a/examples/assets/splats/wave/wave_0198.sog b/examples/assets/splats/wave/wave_0198.sog new file mode 100644 index 00000000000..66e496b20d7 Binary files /dev/null and b/examples/assets/splats/wave/wave_0198.sog differ diff --git a/examples/assets/splats/wave/wave_0199.sog b/examples/assets/splats/wave/wave_0199.sog new file mode 100644 index 00000000000..d126b3f77f3 Binary files /dev/null and b/examples/assets/splats/wave/wave_0199.sog differ diff --git a/examples/assets/splats/wave/wave_0200.sog b/examples/assets/splats/wave/wave_0200.sog new file mode 100644 index 00000000000..fcd41ed6b4e Binary files /dev/null and b/examples/assets/splats/wave/wave_0200.sog differ diff --git a/examples/iframe/loader.mjs b/examples/iframe/loader.mjs index b1a6ad10786..5a10cbe6220 100644 --- a/examples/iframe/loader.mjs +++ b/examples/iframe/loader.mjs @@ -170,7 +170,8 @@ class ExampleLoader { } if (this._app) { - if (this._app.frame) { + // Check if app has already started (frame is a number, including 0) + if (this._app.frame !== undefined) { this._appStart(); } else { this._app.once('start', () => this._appStart()); diff --git a/examples/package-lock.json b/examples/package-lock.json index 5d2ad205b92..918a8e73ca6 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -9,5684 +9,80 @@ "version": "0.0.0", "license": "MIT", "devDependencies": { - "@babel/standalone": "7.26.9", + "@babel/standalone": "7.28.5", "@monaco-editor/react": "4.7.0", - "@playcanvas/eslint-config": "2.0.9", + "@playcanvas/eslint-config": "2.1.0", "@playcanvas/observer": "1.6.6", - "@playcanvas/pcui": "4.6.0", - "@rollup/plugin-commonjs": "28.0.3", - "@rollup/plugin-node-resolve": "15.3.1", - "@rollup/plugin-replace": "6.0.2", + "@playcanvas/pcui": "5.3.3", + "@rollup/plugin-commonjs": "29.0.0", + "@rollup/plugin-node-resolve": "16.0.3", + "@rollup/plugin-replace": "6.0.3", "@rollup/plugin-terser": "0.4.4", "@tweenjs/tween.js": "25.0.0", - "@types/react": "18.3.18", - "@types/react-dom": "18.3.5", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", "@types/react-router-dom": "5.3.3", - "concurrently": "9.1.2", - "cross-env": "7.0.3", - "eslint": "9.22.0", + "concurrently": "9.2.1", + "cross-env": "10.1.0", + "eslint": "9.39.1", "examples": "file:./iframe", "fflate": "0.8.2", - "fs-extra": "11.3.0", - "monaco-editor": "0.33.0", + "fs-extra": "11.3.2", + "monaco-editor": "0.55.1", "playcanvas": "file:..", "prop-types": "15.8.1", - "puppeteer": "23.11.1", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-es6": "1.0.2", - "react-router-dom": "5.3.4", - "rollup": "4.35.0", - "serve": "14.2.4", - "sharp": "0.33.5" + "puppeteer": "24.31.0", + "react": "19.2.0", + "react-dom": "19.2.0", + "react-router-dom": "7.9.6", + "rollup": "4.53.3", + "serve": "14.2.5", + "sharp": "0.34.5" } }, "..": { "name": "playcanvas", - "version": "2.8.0-dev.0", + "version": "2.15.0-beta.0", "dev": true, "license": "MIT", "dependencies": { - "@types/webxr": "^0.5.22", - "@webgpu/types": "^0.1.60" + "@types/webxr": "^0.5.24", + "@webgpu/types": "^0.1.66" }, "devDependencies": { "@playcanvas/eslint-config": "2.1.0", - "@rollup/plugin-node-resolve": "16.0.1", + "@rollup/plugin-node-resolve": "16.0.3", "@rollup/plugin-strip": "3.0.4", "@rollup/plugin-swc": "0.4.0", "@rollup/plugin-terser": "0.4.4", - "@rollup/pluginutils": "5.1.4", - "@swc/core": "1.11.21", - "@types/node": "22.15.3", + "@rollup/pluginutils": "5.3.0", + "@swc/core": "1.15.3", + "@types/node": "24.10.1", "c8": "10.1.3", - "chai": "5.2.0", - "eslint": "9.25.1", + "chai": "6.2.1", + "eslint": "9.39.1", "fflate": "0.8.2", - "globals": "16.0.0", - "jsdom": "26.1.0", - "mocha": "11.1.0", - "publint": "0.3.12", - "rollup": "4.40.1", - "rollup-plugin-dts": "6.2.1", + "globals": "16.5.0", + "jsdom": "27.2.0", + "mocha": "11.7.5", + "nise": "6.1.1", + "publint": "0.3.15", + "rollup": "4.53.3", + "rollup-plugin-dts": "6.3.0", "rollup-plugin-jscc": "2.0.0", - "rollup-plugin-visualizer": "5.14.0", - "serve": "14.2.4", - "sinon": "19.0.5", - "typedoc": "0.28.3", - "typedoc-plugin-mdn-links": "5.0.1", - "typedoc-plugin-missing-exports": "4.0.0", - "typescript": "5.8.3" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "canvas": "3.1.0" - } - }, - "../iframe": { - "extraneous": true - }, - "../node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/@babel/code-frame": { - "version": "7.24.2", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/highlight": { - "version": "7.24.2", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@es-joy/jsdoccomment": { - "version": "0.41.0", - "dev": true, - "license": "MIT", - "dependencies": { - "comment-parser": "1.4.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "../node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "../node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "../node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@eslint/js": { - "version": "8.57.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "../node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "../node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "../node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "dev": true, - "license": "BSD-3-Clause" - }, - "../node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "../node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "dev": true, - "license": "MIT" - }, - "../node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "../node_modules/@jsbits/escape-regex-str": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.2" - } - }, - "../node_modules/@jsbits/get-package-version": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.2" - } - }, - "../node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "../node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/@playcanvas/eslint-config": { - "version": "1.7.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-plugin-import": "^2.28.0", - "eslint-plugin-jsdoc": "^46.4.6" - }, - "peerDependencies": { - "eslint": ">= 4" - } - }, - "../node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" + "rollup-plugin-visualizer": "6.0.5", + "serve": "14.2.5", + "sinon": "21.0.0", + "typedoc": "0.28.15", + "typedoc-plugin-mdn-links": "5.0.10", + "typedoc-plugin-missing-exports": "4.1.2", + "typescript": "5.9.3" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-strip": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "../node_modules/@sinonjs/commons": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "../node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "../node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "../node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "../node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "../node_modules/@types/estree": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/json5": { - "version": "0.0.29", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/node": { - "version": "20.11.30", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "../node_modules/@types/resolve": { - "version": "1.20.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/webxr": { - "version": "0.5.14", - "dev": true, - "license": "MIT" - }, - "../node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/@webgpu/types": { - "version": "0.1.40", - "dev": true, - "license": "BSD-3-Clause" - }, - "../node_modules/@zeit/schemas": { - "version": "2.29.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/accepts": { - "version": "1.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/acorn": { - "version": "8.11.3", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "../node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "../node_modules/agent-base": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "../node_modules/ansi-align": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "../node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "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/ansi-colors": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/arch": { - "version": "2.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "../node_modules/are-docs-informative": { - "version": "0.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "../node_modules/arg": { - "version": "5.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "../node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/array-includes": { - "version": "3.1.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/array.prototype.flat": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "../node_modules/asynckit": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/available-typed-arrays": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/binary-extensions": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/boxen": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.0", - "chalk": "^5.0.1", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/boxen/node_modules/chalk": { - "version": "5.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "../node_modules/braces": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/browser-stdout": { - "version": "1.3.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/builtin-modules": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/c8": { - "version": "9.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=14.14.0" - } - }, - "../node_modules/call-bind": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/camelcase": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/chai": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.0.0", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/chalk-template": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "../node_modules/chalk-template/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/chalk-template/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/chalk-template/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/chalk-template/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/chalk-template/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/chalk-template/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/check-error": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "../node_modules/cli-boxes": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/clipboardy": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "arch": "^2.2.0", - "execa": "^5.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "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/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "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/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "../node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "optional": true - }, - "../node_modules/combined-stream": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/commander": { - "version": "2.20.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/comment-parser": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, - "../node_modules/compressible": { - "version": "2.0.18", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/compression": { - "version": "1.7.4", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "../node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/content-disposition": { - "version": "0.5.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/cssstyle": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "rrweb-cssom": "^0.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/data-urls": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/data-view-buffer": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/data-view-byte-length": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/data-view-byte-offset": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/debug": { - "version": "4.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "../node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/decimal.js": { - "version": "10.4.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/deep-eql": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/deep-extend": { - "version": "0.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "../node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/define-data-property": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/define-lazy-prop": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/define-properties": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/delayed-stream": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "../node_modules/diff": { - "version": "5.0.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "../node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/entities": { - "version": "4.5.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "../node_modules/es-abstract": { - "version": "1.23.2", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/es-define-property": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/es-errors": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/es-object-atoms": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/es-set-tostringtag": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/es-shim-unscopables": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - } - }, - "../node_modules/es-to-primitive": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/escalade": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/eslint": { - "version": "8.57.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "../node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "../node_modules/eslint-module-utils": { - "version": "2.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "../node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "../node_modules/eslint-plugin-import": { - "version": "2.29.1", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "../node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "../node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/eslint-plugin-jsdoc": { - "version": "46.10.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.41.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "../node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/eslint-plugin-jsdoc/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/eslint-plugin-jsdoc/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/espree": { - "version": "9.6.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/esquery": { - "version": "1.5.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "../node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "../node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "../node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/fast-url-parser": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^1.3.2" - } - }, - "../node_modules/fastq": { - "version": "1.17.1", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "../node_modules/fflate": { - "version": "0.8.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/file-entry-cache": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "../node_modules/fill-range": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "../node_modules/flat-cache": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "../node_modules/flatted": { - "version": "3.3.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/for-each": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "../node_modules/foreground-child": { - "version": "3.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/form-data": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/function.prototype.name": { - "version": "1.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/functions-have-names": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "../node_modules/get-func-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "../node_modules/get-intrinsic": { - "version": "1.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/get-symbol-description": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "../node_modules/globals": { - "version": "11.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/globalthis": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/gopd": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/graphemer": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/has-bigints": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=4" - } - }, - "../node_modules/has-property-descriptors": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-proto": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-symbols": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-tostringtag": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/hasown": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "../node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/http-proxy-agent": { - "version": "7.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/https-proxy-agent": { - "version": "7.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "../node_modules/ignore": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "../node_modules/ignore-walk": { - "version": "5.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^5.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/ignore-walk/node_modules/minimatch": { - "version": "5.1.6", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/import-fresh": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "../node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "../node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "license": "ISC" - }, - "../node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "../node_modules/internal-slot": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/is-array-buffer": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-bigint": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-boolean-object": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-builtin-module": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-callable": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-core-module": { - "version": "2.13.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-data-view": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-date-object": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/is-module": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/is-negative-zero": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "../node_modules/is-number-object": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-plain-obj": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-port-reachable": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/is-regex": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-string": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-symbol": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-typed-array": { - "version": "1.1.13", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-unicode-supported": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-weakref": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/isarray": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "../node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/istanbul-reports": { - "version": "3.1.7", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "optional": true - }, - "../node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "../node_modules/jscc": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsbits/escape-regex-str": "^1.0.2", - "@jsbits/get-package-version": "^1.0.2", - "magic-string": "^0.25.1", - "perf-regexes": "^1.0.1", - "skip-regex": "^1.0.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "../node_modules/jscc/node_modules/magic-string": { - "version": "0.25.9", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "../node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "../node_modules/jsdom": { - "version": "24.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cssstyle": "^4.0.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.7", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.16.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^2.11.2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "../node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/jsonc-parser": { - "version": "3.2.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/just-extend": { - "version": "6.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "../node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/lodash.get": { - "version": "4.4.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/log-symbols": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/loupe": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "../node_modules/lunr": { - "version": "2.3.9", - "dev": true, - "license": "MIT" - }, - "../node_modules/magic-string": { - "version": "0.30.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/make-dir/node_modules/semver": { - "version": "7.6.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/marked": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "../node_modules/merge-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/mime-db": { - "version": "1.52.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/mime-types": { - "version": "2.1.35", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "../node_modules/minimist": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/mocha": { - "version": "10.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "../node_modules/mocha/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "../node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "../node_modules/mocha/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/mocha/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/mocha/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/mocha/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "../node_modules/mocha/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "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/mocha/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "../node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "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/mocha/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/mri": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/nise": { - "version": "5.1.9", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "../node_modules/nise/node_modules/path-to-regexp": { - "version": "6.2.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/npm-bundled": { - "version": "2.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/npm-normalize-package-bin": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/npm-packlist": { - "version": "5.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^8.0.1", - "ignore-walk": "^5.0.1", - "npm-bundled": "^2.0.0", - "npm-normalize-package-bin": "^2.0.0" - }, - "bin": { - "npm-packlist": "bin/index.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/npm-packlist/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/npm-packlist/node_modules/glob": { - "version": "8.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm-packlist/node_modules/minimatch": { - "version": "5.1.6", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/nwsapi": { - "version": "2.2.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/object-inspect": { - "version": "1.13.1", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/object-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/object.assign": { - "version": "4.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/object.fromentries": { - "version": "2.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/object.groupby": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/object.values": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/on-headers": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "../node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/open": { - "version": "8.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/optionator": { - "version": "0.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/parse5": { - "version": "7.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "../node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/path-is-inside": { - "version": "1.0.2", - "dev": true, - "license": "(WTFPL OR MIT)" - }, - "../node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/path-to-regexp": { - "version": "2.2.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/pathval": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "../node_modules/perf-regexes": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.14" - } - }, - "../node_modules/picocolors": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "../node_modules/possible-typed-array-names": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/psl": { - "version": "1.9.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/publint": { - "version": "0.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "npm-packlist": "^5.1.3", - "picocolors": "^1.0.0", - "sade": "^1.8.1" - }, - "bin": { - "publint": "lib/cli.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://bjornlu.com/sponsor" - } - }, - "../node_modules/punycode": { - "version": "1.4.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/querystringify": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "../node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "../node_modules/rc": { - "version": "1.2.8", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "../node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/readdirp": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "../node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/registry-auth-token": { - "version": "3.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "../node_modules/registry-url": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/require-from-string": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/requires-port": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/resolve": { - "version": "1.22.8", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/reusify": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "../node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/rollup": { - "version": "4.13.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", - "fsevents": "~2.3.2" - } - }, - "../node_modules/rollup-plugin-dts": { - "version": "6.1.0", - "dev": true, - "license": "LGPL-3.0", - "dependencies": { - "magic-string": "^0.30.4" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/Swatinem" - }, - "optionalDependencies": { - "@babel/code-frame": "^7.22.13" - }, - "peerDependencies": { - "rollup": "^3.29.4 || ^4", - "typescript": "^4.5 || ^5.0" - } - }, - "../node_modules/rollup-plugin-jscc": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsbits/get-package-version": "^1.0.3", - "jscc": "^1.1.1", - "rollup-pluginutils": "^2.8.2" - }, - "engines": { - "node": ">=10.12.0" - }, - "peerDependencies": { - "rollup": ">=2" - } - }, - "../node_modules/rollup-plugin-visualizer": { - "version": "5.12.0", - "dev": true, - "license": "MIT", - "dependencies": { - "open": "^8.4.0", - "picomatch": "^2.3.1", - "source-map": "^0.7.4", - "yargs": "^17.5.1" - }, - "bin": { - "rollup-plugin-visualizer": "dist/bin/cli.js" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "rollup": "2.x || 3.x || 4.x" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/rollup-plugin-visualizer/node_modules/source-map": { - "version": "0.7.4", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "../node_modules/rollup-pluginutils": { - "version": "2.8.2", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "../node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/rrweb-cssom": { - "version": "0.6.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "../node_modules/sade": { - "version": "1.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/safe-array-concat": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/safe-buffer": { - "version": "5.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "../node_modules/safe-regex-test": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/saxes": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "../node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "../node_modules/serialize-javascript": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "../node_modules/serve": { - "version": "14.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@zeit/schemas": "2.29.0", - "ajv": "8.11.0", - "arg": "5.0.2", - "boxen": "7.0.0", - "chalk": "5.0.1", - "chalk-template": "0.4.0", - "clipboardy": "3.0.0", - "compression": "1.7.4", - "is-port-reachable": "4.0.0", - "serve-handler": "6.1.5", - "update-check": "1.5.4" - }, - "bin": { - "serve": "build/main.js" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/serve-handler": { - "version": "6.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - } - }, - "../node_modules/serve-handler/node_modules/bytes": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/serve-handler/node_modules/range-parser": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/serve/node_modules/ajv": { - "version": "8.11.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "../node_modules/serve/node_modules/chalk": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/serve/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/set-function-length": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/set-function-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/shiki": { - "version": "0.14.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "../node_modules/side-channel": { - "version": "1.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/sinon": { - "version": "17.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "../node_modules/sinon/node_modules/diff": { - "version": "5.2.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "../node_modules/sinon/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/skip-regex": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.2" - } - }, - "../node_modules/smob": { - "version": "1.4.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/source-map-support": { - "version": "0.5.21", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "../node_modules/sourcemap-codec": { - "version": "1.4.8", - "dev": true, - "license": "MIT" - }, - "../node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "license": "CC-BY-3.0" - }, - "../node_modules/spdx-expression-parse": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "../node_modules/spdx-license-ids": { - "version": "3.0.17", - "dev": true, - "license": "CC0-1.0" - }, - "../node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "../node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "../node_modules/string.prototype.trim": { - "version": "1.2.9", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/string.prototype.trimend": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/symbol-tree": { - "version": "3.2.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/terser": { - "version": "5.29.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "../node_modules/tough-cookie": { - "version": "4.1.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/tough-cookie/node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "../node_modules/tr46": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/tr46/node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/tsconfig-paths": { - "version": "3.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "../node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "../node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/type-fest": { - "version": "2.19.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/typed-array-buffer": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/typed-array-byte-length": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/typed-array-length": { - "version": "1.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/typedoc": { - "version": "0.25.12", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.7" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 16" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" - } - }, - "../node_modules/typedoc-plugin-mdn-links": { - "version": "3.1.18", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typedoc": ">= 0.23.14 || 0.24.x || 0.25.x" - } - }, - "../node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/typescript": { - "version": "5.4.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "../node_modules/unbox-primitive": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/undici-types": { - "version": "5.26.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/update-check": { - "version": "1.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, - "../node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "../node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/url-parse": { - "version": "1.5.10", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "../node_modules/v8-to-istanbul": { - "version": "9.2.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "../node_modules/vary": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/vscode-oniguruma": { - "version": "1.7.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/vscode-textmate": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/webidl-conversions": { - "version": "7.0.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "../node_modules/whatwg-encoding": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/whatwg-mimetype": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "../node_modules/whatwg-url": { - "version": "14.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/which-boxed-primitive": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/which-typed-array": { - "version": "1.1.15", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/widest-line": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/workerpool": { - "version": "6.2.1", - "dev": true, - "license": "Apache-2.0" - }, - "../node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "../node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "../node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "../node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "../node_modules/ws": { - "version": "8.16.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "../node_modules/xml-name-validator": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "../node_modules/xmlchars": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/yargs": { - "version": "17.7.2", - "dev": true, - "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", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/yargs-unparser": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "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/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "node": ">=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "canvas": "3.2.0" } }, "iframe": { @@ -5694,44 +90,34 @@ "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/standalone": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.26.9.tgz", - "integrity": "sha512-UTeQKy0kzJwWRe55kT1uK4G9H6D0lS6G4207hCU/bDaOhA5t2aC0qHN6GmID0Axv3OFLNXm27NdqcWp+BXcGtA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.28.5.tgz", + "integrity": "sha512-1DViPYJpRU50irpGMfLBQ9B4kyfQuL6X7SS7pwTeWeZX0mNkjzPi0XFqxCjSdddZXUQy4AhnQnnesA/ZHnvAdw==", "dev": true, "license": "MIT", "engines": { @@ -5739,34 +125,46 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", - "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", + "version": "0.50.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", + "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==", "dev": true, + "license": "MIT", "dependencies": { + "@types/estree": "^1.0.6", + "@typescript-eslint/types": "^8.11.0", "comment-parser": "1.4.1", "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -5785,6 +183,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5793,22 +192,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -5817,19 +217,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", - "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5840,9 +243,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5852,7 +255,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -5864,19 +267,22 @@ } }, "node_modules/@eslint/js": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", - "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5884,13 +290,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -5902,41 +308,31 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -5946,9 +342,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5959,14 +355,25 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -5978,17 +385,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -6000,17 +408,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -6020,13 +429,14 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -6036,13 +446,14 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -6052,13 +463,48 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -6068,13 +514,14 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -6084,13 +531,14 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -6100,13 +548,14 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -6116,13 +565,14 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -6132,13 +582,14 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -6150,17 +601,64 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" + "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -6172,17 +670,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -6194,17 +693,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -6216,17 +716,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" + "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -6238,17 +739,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -6260,21 +762,42 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.2.0" + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -6283,13 +806,14 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -6302,13 +826,14 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -6321,28 +846,20 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -6350,22 +867,27 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -6374,9 +896,9 @@ } }, "node_modules/@monaco-editor/loader": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", - "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", "dev": true, "license": "MIT", "dependencies": { @@ -6398,27 +920,15 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/@playcanvas/eslint-config": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@playcanvas/eslint-config/-/eslint-config-2.0.9.tgz", - "integrity": "sha512-v52YxTD/3vinXkAiXE3XVPUReAFLwyzryT2LtWUus3u/rc76r2+2i7uqmFfAdEw5z0Vlk4feMdSypRiiJr0OsQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@playcanvas/eslint-config/-/eslint-config-2.1.0.tgz", + "integrity": "sha512-m8IMRsdmxeSGvmfcl+wYk+dTF7I4wCbB+FozT//yMCUVCj7lHeaWWeHdouRUMgP5FWzEZ6q6u6bjXzOgUlbBeg==", "dev": true, "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsdoc": "^50.5.0", + "eslint-plugin-jsdoc": "^50.6.11", "eslint-plugin-regexp": "^2.7.0" }, "peerDependencies": { @@ -6436,29 +946,35 @@ } }, "node_modules/@playcanvas/pcui": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@playcanvas/pcui/-/pcui-4.6.0.tgz", - "integrity": "sha512-1r1WQak+oMKPmhsYfKq7xiVhsJQREtHTrztVTvCnxCAq75y1Lm3qHWixYZ3YF/RQb8sSNpBoOb1fivXIeHLmew==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@playcanvas/pcui/-/pcui-5.3.3.tgz", + "integrity": "sha512-mdyhuP2CxDR4Ld8VlVfVDff79wuMycpgAdLZ26+cI/rLo3Fa9q5rSUzCRVpb2pPJI0R60zsxM2UWEE9pMBzlfA==", "dev": true, "license": "MIT", "dependencies": { "@playcanvas/observer": "^1.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0" } }, "node_modules/@puppeteer/browsers": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", - "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.13.tgz", + "integrity": "sha512-a9Ruw3j3qlnB5a/zHRTkruppynxqaeE4H9WNj5eYGRWqw0ZauZ23f4W2ARf3hghF5doozyD+CRtt7XSYuYRI/Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.0", + "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.6.3", - "tar-fs": "^3.0.6", - "unbzip2-stream": "^1.4.3", + "semver": "^7.7.3", + "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { @@ -6469,9 +985,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -6482,9 +998,9 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "28.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", - "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", + "integrity": "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6509,9 +1025,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", - "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", "dev": true, "license": "MIT", "dependencies": { @@ -6534,9 +1050,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", "dev": true, "license": "MIT", "dependencies": { @@ -6579,10 +1095,11 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -6601,9 +1118,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", - "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -6615,9 +1132,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", - "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -6629,9 +1146,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", - "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -6643,9 +1160,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", - "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -6657,9 +1174,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", - "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -6671,9 +1188,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", - "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -6685,9 +1202,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", - "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -6699,9 +1216,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", - "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -6713,9 +1230,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", - "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -6727,9 +1244,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", - "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -6740,10 +1257,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", - "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -6754,10 +1271,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", - "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -6769,9 +1286,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", - "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -6783,9 +1314,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", - "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -6797,9 +1328,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", - "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -6811,9 +1342,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", - "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -6824,10 +1355,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", - "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -6839,9 +1384,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", - "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -6852,10 +1397,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", - "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", "cpu": [ "x64" ], @@ -6870,7 +1429,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", @@ -6887,13 +1447,16 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/history": { "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", "dev": true, "license": "MIT" }, @@ -6901,52 +1464,51 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "22.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", - "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~7.16.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-router": { "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6970,41 +1532,55 @@ "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" } }, + "node_modules/@typescript-eslint/types": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@zeit/schemas": { "version": "2.36.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } + "license": "MIT" }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -7017,14 +1593,15 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -7036,6 +1613,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7049,22 +1627,76 @@ }, "node_modules/ansi-align": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.1.0" } }, - "node_modules/ansi-regex": { + "node_modules/ansi-align/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "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/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -7079,6 +1711,8 @@ }, "node_modules/arch": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", "dev": true, "funding": [ { @@ -7101,12 +1735,15 @@ "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/arg": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true, "license": "MIT" }, @@ -7114,16 +1751,18 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -7133,17 +1772,20 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7153,17 +1795,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7173,15 +1817,16 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7191,15 +1836,16 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7209,19 +1855,19 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -7243,11 +1889,22 @@ "node": ">=4" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -7259,35 +1916,55 @@ } }, "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", - "dev": true + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } }, "node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, "license": "Apache-2.0", - "optional": true + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } }, "node_modules/bare-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", - "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", + "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { "bare": ">=1.16.0" @@ -7302,9 +1979,9 @@ } }, "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7324,9 +2001,9 @@ } }, "node_modules/bare-stream": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", - "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7346,25 +2023,16 @@ } } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } }, "node_modules/basic-ftp": { "version": "5.0.5", @@ -7378,6 +2046,8 @@ }, "node_modules/boxen": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", "dev": true, "license": "MIT", "dependencies": { @@ -7397,167 +2067,99 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.0.1", + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "6.2.1", + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.3.0", + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "*" } }, - "node_modules/boxen/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT" }, - "node_modules/boxen/node_modules/string-width": { - "version": "5.1.2", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "2.19.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12.20" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/boxen/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.0.0", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -7571,12 +2173,15 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/camelcase": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true, "license": "MIT", "engines": { @@ -7588,6 +2193,8 @@ }, "node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -7603,6 +2210,8 @@ }, "node_modules/chalk-template": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", "dev": true, "license": "MIT", "dependencies": { @@ -7617,6 +2226,8 @@ }, "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -7627,14 +2238,14 @@ } }, "node_modules/chromium-bidi": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", - "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-11.0.0.tgz", + "integrity": "sha512-cM3DI+OOb89T3wO8cpPSro80Q9eKYJ7hGVXoGS3GkDPxnYSqiv+6xwpIf6XERyJ9Tdsl09hmNmY94BkgZdVekw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "mitt": "3.0.1", - "zod": "3.23.8" + "mitt": "^3.0.1", + "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" @@ -7642,6 +2253,8 @@ }, "node_modules/cli-boxes": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "dev": true, "license": "MIT", "engines": { @@ -7653,6 +2266,8 @@ }, "node_modules/clipboardy": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", "dev": true, "license": "MIT", "dependencies": { @@ -7669,6 +2284,8 @@ }, "node_modules/cliui": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -7680,20 +2297,73 @@ "node": ">=12" } }, - "node_modules/color": { + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "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/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7705,20 +2375,15 @@ }, "node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/commander": { "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT" }, @@ -7727,17 +2392,22 @@ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12.0.0" } }, "node_modules/commondir": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true, "license": "MIT" }, "node_modules/compressible": { "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "license": "MIT", "dependencies": { @@ -7748,16 +2418,18 @@ } }, "node_modules/compression": { - "version": "1.7.4", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { @@ -7766,6 +2438,8 @@ }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { @@ -7774,33 +2448,31 @@ }, "node_modules/compression/node_modules/ms": { "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/concurrently": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", - "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", @@ -7818,15 +2490,31 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -7849,22 +2537,21 @@ } }, "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.1" + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" }, "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=20" } }, "node_modules/cross-spawn": { @@ -7883,7 +2570,9 @@ } }, "node_modules/csstype": { - "version": "3.1.2", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, "license": "MIT" }, @@ -7898,14 +2587,15 @@ } }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7915,29 +2605,31 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -7949,9 +2641,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -7968,6 +2660,8 @@ }, "node_modules/deep-extend": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "license": "MIT", "engines": { @@ -7978,10 +2672,13 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { @@ -7993,6 +2690,7 @@ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -8010,6 +2708,7 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -8038,25 +2737,28 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } }, "node_modules/devtools-protocol": { - "version": "0.0.1367902", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", - "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", - "dev": true + "version": "0.0.1521046", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz", + "integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -8064,21 +2766,51 @@ "node": ">=0.10.0" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/emoji-regex": { - "version": "8.0.0", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -8088,71 +2820,82 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-abstract": { - "version": "1.23.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.4.tgz", - "integrity": "sha512-HR1gxH5OaiN7XH7uiWH0RLw0RcFySiSoW1ctxmD1ahTw3uGBtkmm/ng0tDU1OtYx5OK6EOL5Y6O21cDflG3Jcg==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -8162,13 +2905,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -8178,21 +2919,17 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true - }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -8201,37 +2938,44 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -8241,7 +2985,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -8253,6 +2999,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8283,33 +3030,32 @@ } }, "node_modules/eslint": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", - "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -8348,6 +3094,7 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -8359,15 +3106,17 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -8385,34 +3134,36 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, + "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -8427,27 +3178,28 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.5.0.tgz", - "integrity": "sha512-xTkshfZrUbiSHXBwZ/9d5ulZ2OcHXxSvm/NPo494H/hadLRJwOq5PMV0EUpMqsb9V+kQo+9BAgi6Z7aJtdBp2A==", + "version": "50.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.8.0.tgz", + "integrity": "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.49.0", + "@es-joy/jsdoccomment": "~0.50.2", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.6", + "debug": "^4.4.1", "escape-string-regexp": "^4.0.0", - "espree": "^10.1.0", + "espree": "^10.3.0", "esquery": "^1.6.0", - "parse-imports": "^2.1.1", - "semver": "^7.6.3", - "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.1" + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0" }, "engines": { "node": ">=18" @@ -8457,10 +3209,11 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -8469,9 +3222,9 @@ } }, "node_modules/eslint-plugin-regexp": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.7.0.tgz", - "integrity": "sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.10.0.tgz", + "integrity": "sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==", "dev": true, "license": "MIT", "dependencies": { @@ -8491,9 +3244,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8508,10 +3261,11 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -8520,14 +3274,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8555,6 +3310,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -8567,6 +3323,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -8579,29 +3336,46 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/estree-walker": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/examples": { "resolved": "iframe", "link": true }, "node_modules/execa": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -8624,6 +3398,8 @@ }, "node_modules/execa/node_modules/get-stream": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -8638,6 +3414,7 @@ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -8655,6 +3432,8 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, @@ -8662,34 +3441,42 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.0" } }, "node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -8711,6 +3498,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -8723,6 +3511,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -8739,6 +3528,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -8748,24 +3538,32 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "dev": true, "license": "MIT", "dependencies": { @@ -8783,6 +3581,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -8796,20 +3595,24 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -8823,12 +3626,25 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -8836,16 +3652,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8854,11 +3676,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -8870,14 +3707,15 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -8887,9 +3725,9 @@ } }, "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -8906,6 +3744,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -8918,6 +3757,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -8930,6 +3770,7 @@ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -8942,12 +3783,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8955,20 +3797,28 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -8980,6 +3830,7 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -8988,10 +3839,14 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -9000,10 +3855,11 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9016,6 +3872,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -9031,6 +3888,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -9038,29 +3896,6 @@ "node": ">= 0.4" } }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -9091,46 +3926,30 @@ }, "node_modules/human-signals": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -9147,51 +3966,53 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } }, "node_modules/ini": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -9204,28 +4025,54 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9239,6 +4086,7 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9247,10 +4095,11 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -9262,11 +4111,14 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -9277,12 +4129,14 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9293,6 +4147,8 @@ }, "node_modules/is-docker": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", "bin": { @@ -9310,23 +4166,63 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -9334,8 +4230,23 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-module": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "dev": true, "license": "MIT" }, @@ -9344,6 +4255,7 @@ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9352,12 +4264,14 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9368,6 +4282,8 @@ }, "node_modules/is-port-reachable": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", "dev": true, "license": "MIT", "engines": { @@ -9379,6 +4295,8 @@ }, "node_modules/is-reference": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9386,13 +4304,16 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -9401,13 +4322,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -9418,6 +4353,8 @@ }, "node_modules/is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { @@ -9428,12 +4365,14 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9443,12 +4382,15 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -9458,13 +4400,27 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9473,12 +4429,33 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9486,6 +4463,8 @@ }, "node_modules/is-wsl": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", "dependencies": { @@ -9499,23 +4478,29 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -9523,18 +4508,12 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.0.0" } @@ -9543,31 +4522,36 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -9576,7 +4560,9 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -9591,6 +4577,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -9600,6 +4587,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -9612,13 +4600,15 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -9629,19 +4619,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9662,40 +4650,82 @@ } }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, "node_modules/merge-stream": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, "node_modules/mime-db": { - "version": "1.52.0", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "dev": true, "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, "engines": { "node": ">= 0.6" } }, - "node_modules/mime-types": { - "version": "2.1.35", + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", "dev": true, "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, "engines": { "node": ">= 0.6" } }, "node_modules/mimic-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -9704,6 +4734,8 @@ }, "node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -9715,6 +4747,8 @@ }, "node_modules/minimist": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", "funding": { @@ -9725,29 +4759,38 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/monaco-editor": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", - "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==", + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, "license": "MIT", "engines": { @@ -9766,6 +4809,8 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -9777,6 +4822,8 @@ }, "node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", "engines": { @@ -9784,10 +4831,11 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9800,19 +4848,23 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -9827,6 +4879,7 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -9845,6 +4898,7 @@ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -9855,12 +4909,14 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -9872,7 +4928,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -9884,12 +4942,15 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/onetime": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -9907,6 +4968,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -9919,11 +4981,30 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -9939,6 +5020,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -9988,6 +5070,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -9995,17 +5078,14 @@ "node": ">=6" } }, - "node_modules/parse-imports": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", - "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", "dev": true, + "license": "MIT", "dependencies": { - "es-module-lexer": "^1.5.3", - "slashes": "^3.0.12" - }, - "engines": { - "node": ">= 18" + "parse-statements": "1.0.11" } }, "node_modules/parse-json": { @@ -10013,6 +5093,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -10026,11 +5107,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10039,10 +5128,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true + "dev": true, + "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -10051,6 +5143,8 @@ }, "node_modules/path-parse": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, @@ -10058,25 +5152,29 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -10089,10 +5187,11 @@ "link": true }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -10102,6 +5201,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -10111,6 +5211,7 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -10155,10 +5256,11 @@ "license": "MIT" }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10169,23 +5271,24 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/puppeteer": { - "version": "23.11.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz", - "integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==", + "version": "24.31.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.31.0.tgz", + "integrity": "sha512-q8y5yLxLD8xdZdzNWqdOL43NbfvUOp60SYhaLZQwHC9CdKldxQKXOyJAciOr7oUJfyAH/KgB2wKvqT2sFKoVXA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.6.1", - "chromium-bidi": "0.11.0", + "@puppeteer/browsers": "2.10.13", + "chromium-bidi": "11.0.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1367902", - "puppeteer-core": "23.11.1", + "devtools-protocol": "0.0.1521046", + "puppeteer-core": "24.31.0", "typed-query-selector": "^2.12.0" }, "bin": { @@ -10196,18 +5299,19 @@ } }, "node_modules/puppeteer-core": { - "version": "23.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", - "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", + "version": "24.31.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.31.0.tgz", + "integrity": "sha512-pnAohhSZipWQoFpXuGV7xCZfaGhqcBR9C4pVrU0QSrcMi7tQMH9J9lDBqBvyMAHQqe8HCARuREqFuVKRQOgTvg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.6.1", - "chromium-bidi": "0.11.0", - "debug": "^4.4.0", - "devtools-protocol": "0.0.1367902", + "@puppeteer/browsers": "2.10.13", + "chromium-bidi": "11.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1521046", "typed-query-selector": "^2.12.0", - "ws": "^8.18.0" + "webdriver-bidi-protocol": "0.3.9", + "ws": "^8.18.3" }, "engines": { "node": ">=18" @@ -10215,6 +5319,8 @@ }, "node_modules/randombytes": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10226,12 +5332,15 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { @@ -10246,6 +5355,8 @@ }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "license": "MIT", "engines": { @@ -10253,96 +5364,73 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.0" } }, - "node_modules/react-es6": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/react-es6/-/react-es6-1.0.2.tgz", - "integrity": "sha512-Nw4SDw6JB87+qWwltgyKPQwepxIZ5LrANenBUh+PO6ofUVaml+fBRr8RGakFFVqB7vITKPARmjxpKfdTFAQE9Q==", - "dev": true, - "license": "MIT" - }, "node_modules/react-is": { "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true, "license": "MIT" }, "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", + "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz", + "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "react-router": "7.9.6" + }, + "engines": { + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/react-router/node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" + "react": ">=18", + "react-dom": ">=18" } }, "node_modules/refa": { @@ -10350,6 +5438,7 @@ "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.8.0" }, @@ -10357,17 +5446,35 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/regexp-ast-analysis": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.1" @@ -10377,14 +5484,17 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "set-function-name": "^2.0.2" }, "engines": { @@ -10396,6 +5506,8 @@ }, "node_modules/registry-auth-token": { "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10405,6 +5517,8 @@ }, "node_modules/registry-url": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", "dev": true, "license": "MIT", "dependencies": { @@ -10416,6 +5530,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -10427,22 +5543,28 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { - "version": "1.22.4", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10452,24 +5574,19 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "dev": true - }, "node_modules/rollup": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", - "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -10479,30 +5596,35 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.35.0", - "@rollup/rollup-android-arm64": "4.35.0", - "@rollup/rollup-darwin-arm64": "4.35.0", - "@rollup/rollup-darwin-x64": "4.35.0", - "@rollup/rollup-freebsd-arm64": "4.35.0", - "@rollup/rollup-freebsd-x64": "4.35.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", - "@rollup/rollup-linux-arm-musleabihf": "4.35.0", - "@rollup/rollup-linux-arm64-gnu": "4.35.0", - "@rollup/rollup-linux-arm64-musl": "4.35.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", - "@rollup/rollup-linux-riscv64-gnu": "4.35.0", - "@rollup/rollup-linux-s390x-gnu": "4.35.0", - "@rollup/rollup-linux-x64-gnu": "4.35.0", - "@rollup/rollup-linux-x64-musl": "4.35.0", - "@rollup/rollup-win32-arm64-msvc": "4.35.0", - "@rollup/rollup-win32-ia32-msvc": "4.35.0", - "@rollup/rollup-win32-x64-msvc": "4.35.0", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, "node_modules/rxjs": { - "version": "7.8.1", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -10510,14 +5632,16 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -10529,6 +5653,8 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -10546,15 +5672,15 @@ ], "license": "MIT" }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "isarray": "^2.0.5" }, "engines": { "node": ">= 0.4" @@ -10563,20 +5689,37 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/scslre": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.0", @@ -10591,12 +5734,15 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/serialize-javascript": { - "version": "6.0.1", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -10604,9 +5750,9 @@ } }, "node_modules/serve": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", - "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz", + "integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==", "dev": true, "license": "MIT", "dependencies": { @@ -10617,7 +5763,7 @@ "chalk": "5.0.1", "chalk-template": "0.4.0", "clipboardy": "3.0.0", - "compression": "1.7.4", + "compression": "1.8.1", "is-port-reachable": "4.0.0", "serve-handler": "6.1.6", "update-check": "1.5.4" @@ -10634,6 +5780,7 @@ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", @@ -10644,25 +5791,14 @@ "range-parser": "1.2.0" } }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true, - "dependencies": { - "mime-db": "~1.33.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/serve/node_modules/ajv": { @@ -10702,11 +5838,19 @@ "dev": true, "license": "MIT" }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -10724,6 +5868,7 @@ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -10734,17 +5879,32 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -10753,31 +5913,36 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/sharp/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -10789,6 +5954,8 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -10800,6 +5967,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -10807,23 +5976,30 @@ } }, "node_modules/shell-quote": { - "version": "1.8.1", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -10832,29 +6008,68 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.3.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/slashes": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", - "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", - "dev": true + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" }, "node_modules/smart-buffer": { "version": "4.2.0", @@ -10868,18 +6083,20 @@ } }, "node_modules/smob": { - "version": "1.4.0", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "dev": true, "license": "MIT" }, "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -10904,6 +6121,8 @@ }, "node_modules/source-map": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -10912,6 +6131,8 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -10923,74 +6144,92 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "CC0-1.0" }, "node_modules/state-local": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/streamx": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", - "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, "license": "MIT", "dependencies": { + "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" } }, "node_modules/string-width": { - "version": "4.2.3", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11000,15 +6239,20 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11018,6 +6262,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -11031,14 +6276,19 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-bom": { @@ -11046,12 +6296,15 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/strip-final-newline": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -11063,6 +6316,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -11072,6 +6326,8 @@ }, "node_modules/supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -11086,6 +6342,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { @@ -11095,26 +6353,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/tar-fs": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", - "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "dev": true, "license": "MIT", "dependencies": { @@ -11131,6 +6373,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -11138,12 +6381,14 @@ } }, "node_modules/terser": { - "version": "5.19.2", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -11155,31 +6400,19 @@ } }, "node_modules/text-decoder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", - "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } }, "node_modules/tree-kill": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "bin": { @@ -11191,6 +6424,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, + "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -11202,13 +6436,15 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -11216,31 +6452,46 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -11250,17 +6501,19 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -11270,17 +6523,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -11293,42 +6547,40 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/universalify": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -11337,6 +6589,8 @@ }, "node_modules/update-check": { "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11349,26 +6603,32 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "dev": true - }, "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.9.tgz", + "integrity": "sha512-uIYvlRQ0PwtZR1EzHlTMol1G0lAlmOe6wPykF9a77AK3bkpvZHzIVxRE2ThOx5vjy2zISe0zhwf5rzuUfbo1PQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -11382,32 +6642,17 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -11416,64 +6661,89 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "4.0.1", + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, "license": "MIT", "dependencies": { - "string-width": "^5.0.1" + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.0.1", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "5.1.2", + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "string-width": "^5.0.1" }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/word-wrap": { @@ -11481,37 +6751,55 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/wrap-ansi": { - "version": "7.0.0", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -11530,6 +6818,8 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -11538,6 +6828,8 @@ }, "node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -11555,17 +6847,65 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "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/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -11576,6 +6916,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -11584,10 +6925,11 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/examples/package.json b/examples/package.json index d2694bb8f38..5466efb0f90 100644 --- a/examples/package.json +++ b/examples/package.json @@ -15,36 +15,35 @@ "watch": "npm run -s build:metadata && cross-env NODE_ENV=development rollup -c -w" }, "devDependencies": { - "@babel/standalone": "7.26.9", + "@babel/standalone": "7.28.5", "@monaco-editor/react": "4.7.0", - "@playcanvas/eslint-config": "2.0.9", + "@playcanvas/eslint-config": "2.1.0", "@playcanvas/observer": "1.6.6", - "@playcanvas/pcui": "4.6.0", - "@rollup/plugin-commonjs": "28.0.3", - "@rollup/plugin-node-resolve": "15.3.1", - "@rollup/plugin-replace": "6.0.2", + "@playcanvas/pcui": "5.3.3", + "@rollup/plugin-commonjs": "29.0.0", + "@rollup/plugin-node-resolve": "16.0.3", + "@rollup/plugin-replace": "6.0.3", "@rollup/plugin-terser": "0.4.4", "@tweenjs/tween.js": "25.0.0", - "@types/react": "18.3.18", - "@types/react-dom": "18.3.5", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", "@types/react-router-dom": "5.3.3", - "concurrently": "9.1.2", - "cross-env": "7.0.3", - "eslint": "9.22.0", + "concurrently": "9.2.1", + "cross-env": "10.1.0", + "eslint": "9.39.1", "examples": "file:./iframe", "fflate": "0.8.2", - "fs-extra": "11.3.0", - "monaco-editor": "0.33.0", + "fs-extra": "11.3.2", + "monaco-editor": "0.55.1", "playcanvas": "file:..", "prop-types": "15.8.1", - "puppeteer": "23.11.1", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-es6": "1.0.2", - "react-router-dom": "5.3.4", - "rollup": "4.35.0", - "serve": "14.2.4", - "sharp": "0.33.5" + "puppeteer": "24.31.0", + "react": "19.2.0", + "react-dom": "19.2.0", + "react-router-dom": "7.9.6", + "rollup": "4.53.3", + "serve": "14.2.5", + "sharp": "0.34.5" }, "author": "PlayCanvas ", "license": "MIT" diff --git a/examples/rollup.config.mjs b/examples/rollup.config.mjs index eead0c2b883..9fe2c1c503c 100644 --- a/examples/rollup.config.mjs +++ b/examples/rollup.config.mjs @@ -261,6 +261,17 @@ const APP_TARGETS = [{ format: 'umd' }, treeshake: 'smallest', + onwarn(warning, warn) { + // Suppress "use client" directive warnings from react-router v7+. + // These directives are for React Server Components which we don't use. + // The directive is safely ignored and has no effect on client-only builds. + // This can be removed if Rollup adds native support for "use client" directives, + // or if we switch to a bundler that supports them (e.g., Vite, webpack 5+). + if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes('"use client"')) { + return; + } + warn(warning); + }, plugins: [ commonjs(), treeshakeIgnore([/@playcanvas\/pcui/g]), // ignore PCUI treeshake diff --git a/examples/scripts/build-thumbnails.mjs b/examples/scripts/build-thumbnails.mjs index db2def9b544..10621fe795d 100644 --- a/examples/scripts/build-thumbnails.mjs +++ b/examples/scripts/build-thumbnails.mjs @@ -4,7 +4,7 @@ import fs from 'fs'; import { spawn, execSync } from 'node:child_process'; -import puppeteer from 'puppeteer'; +import { launch } from 'puppeteer'; import sharp from 'sharp'; import { exampleMetaData } from '../cache/metadata.mjs'; @@ -60,12 +60,12 @@ class PuppeteerPool { } /** - * @param {import("puppeteer").PuppeteerLaunchOptions} options - Launch options. + * @param {Parameters[0]} options - Launch options. */ async launch(options = {}) { const promises = []; for (let i = 0; i < this._size; i++) { - promises.push(puppeteer.launch(options)); + promises.push(launch(options)); } const browsers = await Promise.all(promises); @@ -224,7 +224,9 @@ const main = async () => { console.log('Spawn server on', PORT); const isWin = process.platform === 'win32'; const cmd = isWin ? 'npx.cmd' : 'npx'; - const server = spawn(cmd, ['serve', 'dist', '-l', PORT, '--no-request-logging', '--config', '../serve.json']); + const server = spawn(cmd, ['serve', 'dist', '-l', PORT, '--no-request-logging', '--config', '../serve.json'], { + shell: true + }); await sleep(1000); // give a second to spawn server console.log('Starting puppeteer screenshot process'); try { diff --git a/examples/src/app/components/ErrorBoundary.mjs b/examples/src/app/components/ErrorBoundary.mjs index de63e82c77a..4a7221b3e2f 100644 --- a/examples/src/app/components/ErrorBoundary.mjs +++ b/examples/src/app/components/ErrorBoundary.mjs @@ -1,7 +1,7 @@ import { Label } from '@playcanvas/pcui/react'; import { Component } from 'react'; -import { fragment, jsx } from '../jsx.mjs'; +import { jsx } from '../jsx.mjs'; /** @@ -83,12 +83,10 @@ class ErrorBoundary extends TypedComponent { render() { if (this.state.hasError) { - return fragment( - jsx(Label, { - id: 'errorLabel', - text: 'RENDER FAILED' - }) - ); + return jsx(Label, { + id: 'errorLabel', + text: 'RENDER FAILED' + }); } return this.props.children; } diff --git a/examples/src/app/components/Example.mjs b/examples/src/app/components/Example.mjs index d08cc9bc07c..7b00f551a98 100644 --- a/examples/src/app/components/Example.mjs +++ b/examples/src/app/components/Example.mjs @@ -2,7 +2,7 @@ import * as PCUI from '@playcanvas/pcui'; import * as ReactPCUI from '@playcanvas/pcui/react'; import { Panel, Container, Button, Spinner } from '@playcanvas/pcui/react'; import React, { Component } from 'react'; -import { withRouter } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { CodeEditorMobile } from './code-editor/CodeEditorMobile.mjs'; import { DeviceSelector } from './DeviceSelector.mjs'; @@ -309,7 +309,9 @@ class Example extends TypedComponent { renderPortrait() { const { collapsed, show, files, description } = this.state; - return fragment( + return jsx( + 'div', + { style: { display: 'contents' } }, jsx( Panel, { @@ -370,7 +372,9 @@ class Example extends TypedComponent { renderLandscape() { const { collapsed } = this.state; - return fragment( + return jsx( + 'div', + { style: { display: 'contents' } }, jsx( Panel, { @@ -407,7 +411,14 @@ class Example extends TypedComponent { } } -// @ts-ignore -const ExampleWithRouter = withRouter(Example); +/** + * Wrapper component to provide router params to the class component. + * @returns {JSX.Element} The Example component with router params. + */ +function ExampleWithRouter() { + const params = useParams(); + // @ts-ignore + return jsx(Example, { match: { params } }); +} export { ExampleWithRouter as Example }; diff --git a/examples/src/app/components/MainLayout.mjs b/examples/src/app/components/MainLayout.mjs index 9993870398a..d9b867c1d9a 100644 --- a/examples/src/app/components/MainLayout.mjs +++ b/examples/src/app/components/MainLayout.mjs @@ -1,6 +1,6 @@ import { Container } from '@playcanvas/pcui/react'; import { Component } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { HashRouter, Routes, Route, Navigate } from 'react-router-dom'; import { CodeEditorDesktop } from './code-editor/CodeEditorDesktop.mjs'; import { Example } from './Example.mjs'; @@ -67,27 +67,33 @@ class MainLayout extends TypedComponent { HashRouter, null, jsx( - Switch, + Routes, null, - jsx(Route, { exact: true, path: '/' }, jsx(Redirect, { to: '/misc/hello-world' })), - jsx( - Route, - { path: '/:category/:example' }, - jsx(SideBar, null), - jsx( - Container, - { id: 'main-view-wrapper' }, - jsx(Menu, { - setShowMiniStats: this.updateShowMiniStats.bind(this) - }), + jsx(Route, { + path: '/', + element: jsx(Navigate, { to: '/misc/hello-world', replace: true }) + }), + jsx(Route, { + path: '/:category/:example', + element: jsx( + 'div', + { id: 'appInner-router', style: { display: 'contents' } }, + jsx(SideBar, null), jsx( Container, - { id: 'main-view' }, - orientation === 'landscape' && jsx(CodeEditorDesktop), - jsx(Example, null) + { id: 'main-view-wrapper' }, + jsx(Menu, { + setShowMiniStats: this.updateShowMiniStats.bind(this) + }), + jsx( + Container, + { id: 'main-view' }, + orientation === 'landscape' && jsx(CodeEditorDesktop), + jsx(Example, null) + ) ) ) - ) + }) ) ) ); diff --git a/examples/src/app/components/Sidebar.mjs b/examples/src/app/components/Sidebar.mjs index 6ed11464ffd..632fd479d44 100644 --- a/examples/src/app/components/Sidebar.mjs +++ b/examples/src/app/components/Sidebar.mjs @@ -1,7 +1,7 @@ import { Observer } from '@playcanvas/observer'; import { BindingTwoWay, BooleanInput, Container, Label, LabelGroup, Panel, TextInput } from '@playcanvas/pcui/react'; import { Component } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { exampleMetaData } from '../../../cache/metadata.mjs'; import { MIN_DESKTOP_WIDTH, VERSION } from '../constants.mjs'; @@ -10,16 +10,15 @@ import { jsx } from '../jsx.mjs'; import { thumbnailPath } from '../paths.mjs'; import { getOrientation } from '../utils.mjs'; -// eslint-disable-next-line jsdoc/require-property /** * @typedef {object} Props + * @property {{ pathname: string, hash: string }} location - The router location. */ /** * @typedef {object} State * @property {Record>} defaultCategories - The default categories. * @property {Record>|null} filteredCategories - The filtered categories. - * @property {string} hash - The hash. * @property {Observer} observer - The observer. * @property {boolean} collapsed - Collapsed or not. * @property {string} orientation - Current orientation. @@ -52,7 +51,6 @@ class SideBar extends TypedComponent { state = { defaultCategories: getDefaultExampleFiles(), filteredCategories: null, - hash: location.hash, observer: new Observer({ largeThumbnails: false }), // @ts-ignore collapsed: localStorage.getItem('sideBarCollapsed') === 'true' || window.top.innerWidth < MIN_DESKTOP_WIDTH, @@ -99,9 +97,6 @@ class SideBar extends TypedComponent { if (!sideBar) { return; } - window.addEventListener('hashchange', () => { - this.mergeState({ hash: location.hash }); - }); this.state.observer.on('largeThumbnails:set', () => { let minTopNavItemDistance = Number.MAX_VALUE; @@ -207,7 +202,7 @@ class SideBar extends TypedComponent { if (Object.keys(categories).length === 0) { return jsx(Label, { text: 'No results' }); } - const { hash } = this.state; + const { pathname } = this.props.location; return Object.keys(categories) .sort((a, b) => (a > b ? 1 : -1)) .map((category) => { @@ -229,7 +224,7 @@ class SideBar extends TypedComponent { .sort((a, b) => (a > b ? 1 : -1)) .map((example) => { const path = `/${category}/${example}`; - const isSelected = new RegExp(`${path}$`).test(hash); + const isSelected = pathname === path; const className = `nav-item ${isSelected ? 'selected' : null}`; return jsx( Link, @@ -303,4 +298,13 @@ class SideBar extends TypedComponent { } } -export { SideBar }; +/** + * Wrapper component to provide router location to the class component. + * @returns {JSX.Element} The SideBar component with router location. + */ +function SideBarWithRouter() { + const location = useLocation(); + return jsx(SideBar, { location }); +} + +export { SideBarWithRouter as SideBar }; diff --git a/examples/src/examples/animation/blend-trees-1d.example.mjs b/examples/src/examples/animation/blend-trees-1d.example.mjs index 55c0621fb3c..497ecb2a926 100644 --- a/examples/src/examples/animation/blend-trees-1d.example.mjs +++ b/examples/src/examples/animation/blend-trees-1d.example.mjs @@ -16,14 +16,11 @@ const assets = { 'texture', { url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false } - ), - bloom: new pc.Asset('bloom', 'script', { url: `${rootPath}/static/scripts/posteffects/posteffect-bloom.js` }) + ) }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -36,13 +33,11 @@ createOptions.componentSystems = [ pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem, - pc.ScriptComponentSystem, pc.AnimComponentSystem ]; createOptions.resourceHandlers = [ pc.TextureHandler, pc.ContainerHandler, - pc.ScriptHandler, pc.AnimClipHandler, pc.AnimStateGraphHandler ]; @@ -74,16 +69,6 @@ assetListLoader.load(() => { clearColor: new pc.Color(0.1, 0.1, 0.1) }); cameraEntity.translate(0, 0.75, 3); - - // add bloom postprocessing (this is ignored by the picker) - cameraEntity.addComponent('script'); - cameraEntity.script.create('bloom', { - attributes: { - bloomIntensity: 1, - bloomThreshold: 0.7, - blurAmount: 4 - } - }); app.root.addChild(cameraEntity); // Create an entity with a light component diff --git a/examples/src/examples/animation/blend-trees-2d-cartesian.controls.mjs b/examples/src/examples/animation/blend-trees-2d-cartesian.controls.mjs index 9c85e18d5f1..7fba7ecd0ea 100644 --- a/examples/src/examples/animation/blend-trees-2d-cartesian.controls.mjs +++ b/examples/src/examples/animation/blend-trees-2d-cartesian.controls.mjs @@ -4,7 +4,7 @@ import * as pc from 'playcanvas'; * @param {import('../../app/components/Example.mjs').ControlOptions} options - The options. * @returns {JSX.Element} The returned JSX Element. */ -export const controls = ({ React, jsx, fragment }) => { +export const controls = ({ observer, React, jsx, fragment }) => { const { createRef, Component } = React; class JsxControls extends Component { position = new pc.Vec2(); @@ -13,7 +13,7 @@ export const controls = ({ React, jsx, fragment }) => { refCanvas = createRef(); mouseEvent(e) { - const { position, modelEntity, width, canvas } = this; + const { position, width, canvas } = this; if (e.targetTouches) { const offset = canvas.getBoundingClientRect(); position @@ -31,25 +31,13 @@ export const controls = ({ React, jsx, fragment }) => { } } position.y *= -1.0; - modelEntity.anim.setFloat('posX', position.x); - modelEntity.anim.setFloat('posY', position.y); - this.drawPosition(); + observer.set('data.pos', { x: position.x, y: position.y }); } get canvas() { return this.refCanvas.current; } - /** @type {pc.Entity} */ - get modelEntity() { - return this.app.root.findByName('model'); - } - - /** @type {pc.Application | undefined} */ - get app() { - return window.top?.pc.app; - } - /** @type {number} */ get width() { return window.top.controlPanel.offsetWidth; @@ -61,11 +49,13 @@ export const controls = ({ React, jsx, fragment }) => { } drawPosition() { - const { canvas, modelEntity, width, height } = this; - if (!modelEntity) { - console.warn('no modelEntity yet'); + const { canvas, width, height } = this; + if (!canvas) { return; } + const animPoints = observer.get('data.animPoints') || []; + const pos = observer.get('data.pos') || { x: 0, y: 0 }; + const ctx = canvas.getContext('2d'); const halfWidth = Math.floor(width / 2); const halfHeight = Math.floor(height / 2); @@ -76,32 +66,31 @@ export const controls = ({ React, jsx, fragment }) => { ctx.fillRect(halfWidth, 0, 1, height); ctx.fillRect(0, halfHeight, width, 1); ctx.fillStyle = '#232e30'; - // @ts-ignore engine-tsd - modelEntity.anim.baseLayer._controller._states.Emote.animations.forEach((animNode) => { - if (animNode.point) { - const posX = (animNode.point.x + 1) * halfWidth; - const posY = (animNode.point.y * -1 + 1) * halfHeight; - const width = 8; - const height = 8; - ctx.fillStyle = '#ffffff80'; - ctx.beginPath(); - ctx.arc(posX, posY, halfWidth * 0.5 * animNode.weight, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = '#283538'; - ctx.beginPath(); - ctx.moveTo(posX, posY - height / 2); - ctx.lineTo(posX - width / 2, posY); - ctx.lineTo(posX, posY + height / 2); - ctx.lineTo(posX + width / 2, posY); - ctx.closePath(); - ctx.fill(); - } + + animPoints.forEach((animNode) => { + const posX = (animNode.x + 1) * halfWidth; + const posY = (animNode.y * -1 + 1) * halfHeight; + const width = 8; + const height = 8; + ctx.fillStyle = '#ffffff80'; + ctx.beginPath(); + ctx.arc(posX, posY, halfWidth * 0.5 * animNode.weight, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = '#283538'; + ctx.beginPath(); + ctx.moveTo(posX, posY - height / 2); + ctx.lineTo(posX - width / 2, posY); + ctx.lineTo(posX, posY + height / 2); + ctx.lineTo(posX + width / 2, posY); + ctx.closePath(); + ctx.fill(); }); + ctx.fillStyle = '#F60'; ctx.beginPath(); ctx.arc( - (modelEntity.anim.getFloat('posX') + 1) * halfWidth, - (modelEntity.anim.getFloat('posY') * -1 + 1) * halfHeight, + (pos.x + 1) * halfWidth, + (pos.y * -1 + 1) * halfHeight, 5, 0, 2 * Math.PI @@ -111,28 +100,20 @@ export const controls = ({ React, jsx, fragment }) => { ctx.stroke(); } - onAppStart() { + componentDidMount() { const { canvas } = this; + canvas.addEventListener('mousemove', this.mouseEvent.bind(this)); + canvas.addEventListener('mousedown', this.mouseEvent.bind(this)); + canvas.addEventListener('touchmove', this.mouseEvent.bind(this)); + canvas.addEventListener('touchstart', this.mouseEvent.bind(this)); + // @ts-ignore engine-tsd const dim = `${window.top.controlPanel.offsetWidth}px`; canvas.setAttribute('style', `width: ${dim}; height: ${dim};`); canvas.setAttribute('width', dim); canvas.setAttribute('height', dim); - this.drawPosition(); - } - componentDidMount() { - const { canvas, app } = this; - // console.log("componentDidMount()", { canvas, app }); - canvas.addEventListener('mousemove', this.mouseEvent.bind(this)); - canvas.addEventListener('mousedown', this.mouseEvent.bind(this)); - canvas.addEventListener('touchmove', this.mouseEvent.bind(this)); - canvas.addEventListener('touchstart', this.mouseEvent.bind(this)); - if (!app) { - console.warn('no app'); - return; - } - this.onAppStart(); + observer.on('*:set', this.drawPosition.bind(this)); } render() { diff --git a/examples/src/examples/animation/blend-trees-2d-cartesian.example.mjs b/examples/src/examples/animation/blend-trees-2d-cartesian.example.mjs index bee9cfd5cb2..82de1bc4ada 100644 --- a/examples/src/examples/animation/blend-trees-2d-cartesian.example.mjs +++ b/examples/src/examples/animation/blend-trees-2d-cartesian.example.mjs @@ -1,3 +1,4 @@ +import { data } from 'examples/observer'; import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -19,14 +20,11 @@ const assets = { 'texture', { url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false } - ), - bloom: new pc.Asset('bloom', 'script', { url: `${rootPath}/static/scripts/posteffects/posteffect-bloom.js` }) + ) }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -42,13 +40,11 @@ createOptions.componentSystems = [ pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem, - pc.ScriptComponentSystem, pc.AnimComponentSystem ]; createOptions.resourceHandlers = [ pc.TextureHandler, pc.ContainerHandler, - pc.ScriptHandler, pc.AnimClipHandler, pc.AnimStateGraphHandler ]; @@ -80,15 +76,6 @@ assetListLoader.load(() => { clearColor: new pc.Color(0.1, 0.1, 0.1) }); cameraEntity.translate(0, 0.75, 3); - // add bloom postprocessing (this is ignored by the picker) - cameraEntity.addComponent('script'); - cameraEntity.script.create('bloom', { - attributes: { - bloomIntensity: 1, - bloomThreshold: 0.7, - blurAmount: 4 - } - }); app.root.addChild(cameraEntity); // Create an entity with a light component @@ -185,6 +172,33 @@ assetListLoader.load(() => { characterStateLayer.assignAnimation('Emote.Dance', assets.danceAnim.resource.animations[0].resource); characterStateLayer.assignAnimation('Emote.Walk', assets.walkAnim.resource.animations[0].resource); + // Initialize observer data + data.set('data', { + pos: { x: -0.5, y: 0.5 }, + animPoints: [] + }); + + // Helper to update animation points for visualization + const updateAnimPoints = () => { + const points = characterStateLayer._controller._states.Emote.animations.map((/** @type {any} */ animNode) => ({ + x: animNode.point?.x ?? 0, + y: animNode.point?.y ?? 0, + weight: animNode.weight ?? 0 + })); + data.set('data.animPoints', points); + }; + + // Set initial animation points + updateAnimPoints(); + + // Listen for position changes from controls + data.on('data.pos:set', (value) => { + modelEntity.anim.setFloat('posX', value.x); + modelEntity.anim.setFloat('posY', value.y); + // Update animation points when position changes (weights recalculate) + updateAnimPoints(); + }); + app.root.addChild(modelEntity); app.start(); diff --git a/examples/src/examples/animation/blend-trees-2d-directional.controls.mjs b/examples/src/examples/animation/blend-trees-2d-directional.controls.mjs index eba5352ef5a..59609d1683f 100644 --- a/examples/src/examples/animation/blend-trees-2d-directional.controls.mjs +++ b/examples/src/examples/animation/blend-trees-2d-directional.controls.mjs @@ -1,18 +1,15 @@ -import * as pc from 'playcanvas'; - /** * @param {import('../../app/components/Example.mjs').ControlOptions} options - The options. * @returns {JSX.Element} The returned JSX Element. */ -export const controls = ({ React, jsx, fragment }) => { +export const controls = ({ observer, React, jsx, fragment }) => { const { useEffect, useRef } = React; /** @type {React.MutableRefObject} */ const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; - // @ts-ignore engine-tsd - /** @type {pc.Entity} */ - const modelEntity = pc.app.root.findByName('model'); + if (!canvas) return; + const width = window.top.controlPanel.offsetWidth; const height = width; const halfWidth = Math.floor(width / 2); @@ -21,8 +18,11 @@ export const controls = ({ React, jsx, fragment }) => { canvas.setAttribute('width', width); canvas.setAttribute('height', height); const ctx = canvas.getContext('2d'); - let position = new pc.Vec2(); - const drawPosition = (ctx) => { + + const drawPosition = () => { + const animPoints = observer.get('data.animPoints') || []; + const pos = observer.get('data.pos') || { x: 0, y: 0 }; + ctx.clearRect(0, 0, width, height); ctx.fillStyle = 'rgba(128, 128, 128, 0.5)'; ctx.fillRect(0, 0, width, height); @@ -30,34 +30,33 @@ export const controls = ({ React, jsx, fragment }) => { ctx.fillRect(halfWidth, 0, 1, height); ctx.fillRect(0, halfHeight, width, 1); ctx.fillStyle = '#232e30'; - // @ts-ignore engine-tsd - modelEntity.anim?.baseLayer._controller._states.Travel.animations.forEach((animNode) => { - if (animNode.point) { - const posX = (animNode.point.x + 1) * halfWidth; - const posY = (animNode.point.y * -1 + 1) * halfHeight; - const width = 8; - const height = 8; - ctx.fillStyle = '#ffffff80'; - ctx.beginPath(); - ctx.arc(posX, posY, halfWidth * 0.5 * animNode.weight, 0, 2 * Math.PI); - ctx.fill(); + animPoints.forEach((animNode) => { + const pointX = (animNode.x + 1) * halfWidth; + const pointY = (animNode.y * -1 + 1) * halfHeight; + const dotWidth = 8; + const dotHeight = 8; - ctx.fillStyle = '#283538'; - ctx.beginPath(); - ctx.moveTo(posX, posY - height / 2); - ctx.lineTo(posX - width / 2, posY); - ctx.lineTo(posX, posY + height / 2); - ctx.lineTo(posX + width / 2, posY); - ctx.closePath(); - ctx.fill(); - } + ctx.fillStyle = '#ffffff80'; + ctx.beginPath(); + ctx.arc(pointX, pointY, halfWidth * 0.5 * animNode.weight, 0, 2 * Math.PI); + ctx.fill(); + + ctx.fillStyle = '#283538'; + ctx.beginPath(); + ctx.moveTo(pointX, pointY - dotHeight / 2); + ctx.lineTo(pointX - dotWidth / 2, pointY); + ctx.lineTo(pointX, pointY + dotHeight / 2); + ctx.lineTo(pointX + dotWidth / 2, pointY); + ctx.closePath(); + ctx.fill(); }); + ctx.fillStyle = '#F60'; ctx.beginPath(); ctx.arc( - (modelEntity.anim.getFloat('posX') + 1) * halfWidth, - (modelEntity.anim.getFloat('posY') * -1 + 1) * halfHeight, + (pos.x + 1) * halfWidth, + (pos.y * -1 + 1) * halfHeight, 5, 0, 2 * Math.PI @@ -66,18 +65,30 @@ export const controls = ({ React, jsx, fragment }) => { ctx.fillStyle = '#283538'; ctx.stroke(); }; - drawPosition(ctx); + + + // Initial draw + drawPosition(); + + observer.on('*:set', drawPosition); + const mouseEvent = (e) => { if (e.buttons) { - position = new pc.Vec2(e.offsetX, e.offsetY).mulScalar(1 / (width / 2)).sub(pc.Vec2.ONE); - position.y *= -1.0; - modelEntity.anim.setFloat('posX', position.x); - modelEntity.anim.setFloat('posY', position.y); - drawPosition(ctx); + const rect = canvas.getBoundingClientRect(); + const x = ((e.clientX - rect.left) / (width / 2)) - 1; + const y = -(((e.clientY - rect.top) / (height / 2)) - 1); + observer.set('data.pos', { x, y }); } }; + canvas.addEventListener('mousemove', mouseEvent); canvas.addEventListener('mousedown', mouseEvent); - }); + + return () => { + canvas.removeEventListener('mousemove', mouseEvent); + canvas.removeEventListener('mousedown', mouseEvent); + }; + }, [observer]); + return fragment(jsx('canvas', { ref: canvasRef })); }; diff --git a/examples/src/examples/animation/blend-trees-2d-directional.example.mjs b/examples/src/examples/animation/blend-trees-2d-directional.example.mjs index 782ae873611..6e8dccb7f06 100644 --- a/examples/src/examples/animation/blend-trees-2d-directional.example.mjs +++ b/examples/src/examples/animation/blend-trees-2d-directional.example.mjs @@ -1,3 +1,4 @@ +import { data } from 'examples/observer'; import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -17,14 +18,11 @@ const assets = { 'texture', { url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false } - ), - bloom: new pc.Asset('bloom', 'script', { url: `${rootPath}/static/scripts/posteffects/posteffect-bloom.js` }) + ) }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -40,13 +38,11 @@ createOptions.componentSystems = [ pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem, - pc.ScriptComponentSystem, pc.AnimComponentSystem ]; createOptions.resourceHandlers = [ pc.TextureHandler, pc.ContainerHandler, - pc.ScriptHandler, pc.AnimClipHandler, pc.AnimStateGraphHandler ]; @@ -78,15 +74,6 @@ assetListLoader.load(() => { clearColor: new pc.Color(0.1, 0.1, 0.1) }); cameraEntity.translate(0, 0.75, 3); - // add bloom postprocessing (this is ignored by the picker) - cameraEntity.addComponent('script'); - cameraEntity.script.create('bloom', { - attributes: { - bloomIntensity: 1, - bloomThreshold: 0.7, - blurAmount: 4 - } - }); app.root.addChild(cameraEntity); // Create an entity with a light component @@ -187,8 +174,34 @@ assetListLoader.load(() => { locomotionLayer.assignAnimation('Travel.WalkBackwards', assets.walkAnim.resource.animations[0].resource); locomotionLayer.assignAnimation('Travel.Jog', assets.jogAnim.resource.animations[0].resource); - app.root.addChild(modelEntity); + // Initialize observer data + data.set('data', { + pos: { x: 0, y: 0 }, + animPoints: [] + }); + + // Helper to update animation points for visualization + const updateAnimPoints = () => { + const points = locomotionLayer._controller._states.Travel.animations.map(animNode => ({ + x: animNode.point?.x ?? 0, + y: animNode.point?.y ?? 0, + weight: animNode.weight ?? 0 + })); + data.set('data.animPoints', points); + }; + + // Set initial animation points + updateAnimPoints(); + // Listen for position changes from controls + data.on('data.pos:set', (value) => { + modelEntity.anim.setFloat('posX', value.x); + modelEntity.anim.setFloat('posY', value.y); + // Update animation points when position changes (weights recalculate) + updateAnimPoints(); + }); + + app.root.addChild(modelEntity); app.start(); }); diff --git a/examples/src/examples/animation/component-properties.example.mjs b/examples/src/examples/animation/component-properties.example.mjs index e7e01b97e3a..d33a8d91c8c 100644 --- a/examples/src/examples/animation/component-properties.example.mjs +++ b/examples/src/examples/animation/component-properties.example.mjs @@ -12,9 +12,7 @@ const assets = { }) }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/animation/events.example.mjs b/examples/src/examples/animation/events.example.mjs index 8d3c856d2ed..c79a4124aee 100644 --- a/examples/src/examples/animation/events.example.mjs +++ b/examples/src/examples/animation/events.example.mjs @@ -15,9 +15,7 @@ const assets = { ) }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/animation/layer-masks.example.mjs b/examples/src/examples/animation/layer-masks.example.mjs index 885dfd3c341..9da4b500584 100644 --- a/examples/src/examples/animation/layer-masks.example.mjs +++ b/examples/src/examples/animation/layer-masks.example.mjs @@ -20,14 +20,11 @@ const assets = { 'texture', { url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false } - ), - bloom: new pc.Asset('bloom', 'script', { url: `${rootPath}/static/scripts/posteffects/posteffect-bloom.js` }) + ) }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -42,13 +39,11 @@ createOptions.componentSystems = [ pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem, - pc.ScriptComponentSystem, pc.AnimComponentSystem ]; createOptions.resourceHandlers = [ pc.TextureHandler, pc.ContainerHandler, - pc.ScriptHandler, pc.AnimClipHandler, pc.AnimStateGraphHandler ]; @@ -95,16 +90,6 @@ assetListLoader.load(() => { clearColor: new pc.Color(0.1, 0.1, 0.1) }); cameraEntity.translate(0, 0.75, 3); - - // add bloom postprocessing (this is ignored by the picker) - cameraEntity.addComponent('script'); - cameraEntity.script.create('bloom', { - attributes: { - bloomIntensity: 1, - bloomThreshold: 0.7, - blurAmount: 4 - } - }); app.root.addChild(cameraEntity); // Create an entity with a light component diff --git a/examples/src/examples/animation/locomotion.example.mjs b/examples/src/examples/animation/locomotion.example.mjs index 8ff126511df..ee0fa0e34ab 100644 --- a/examples/src/examples/animation/locomotion.example.mjs +++ b/examples/src/examples/animation/locomotion.example.mjs @@ -34,9 +34,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/animation/tween.example.mjs b/examples/src/examples/animation/tween.example.mjs index 239c2df3549..44af92a1fad 100644 --- a/examples/src/examples/animation/tween.example.mjs +++ b/examples/src/examples/animation/tween.example.mjs @@ -12,9 +12,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/camera/first-person.example.mjs b/examples/src/examples/camera/first-person.example.mjs index add2ecfd423..64221b51338 100644 --- a/examples/src/examples/camera/first-person.example.mjs +++ b/examples/src/examples/camera/first-person.example.mjs @@ -18,9 +18,7 @@ await new Promise((resolve) => { }); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const assets = { diff --git a/examples/src/examples/camera/fly.example.mjs b/examples/src/examples/camera/fly.example.mjs index 5e37d6cab8e..9c51efcd895 100644 --- a/examples/src/examples/camera/fly.example.mjs +++ b/examples/src/examples/camera/fly.example.mjs @@ -14,9 +14,7 @@ if (!(canvas instanceof HTMLCanvasElement)) { window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const assets = { diff --git a/examples/src/examples/camera/multi.controls.mjs b/examples/src/examples/camera/multi.controls.mjs index 1f9b6c34131..23a408ac5a0 100644 --- a/examples/src/examples/camera/multi.controls.mjs +++ b/examples/src/examples/camera/multi.controls.mjs @@ -66,7 +66,7 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { binding: new BindingTwoWay(), link: { observer, path: 'attr.moveSpeed' }, min: 1, - max: 10 + max: 100 }) ), jsx( @@ -76,7 +76,7 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { binding: new BindingTwoWay(), link: { observer, path: 'attr.moveFastSpeed' }, min: 1, - max: 10 + max: 100 }) ), jsx( @@ -86,7 +86,7 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { binding: new BindingTwoWay(), link: { observer, path: 'attr.moveSlowSpeed' }, min: 1, - max: 10 + max: 100 }) ), jsx( diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index 2dab69beb14..ab1cf4c077f 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -14,9 +14,7 @@ if (!(canvas instanceof HTMLCanvasElement)) { window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const assets = { diff --git a/examples/src/examples/camera/orbit.example.mjs b/examples/src/examples/camera/orbit.example.mjs index e268eb036d3..0f90638d144 100644 --- a/examples/src/examples/camera/orbit.example.mjs +++ b/examples/src/examples/camera/orbit.example.mjs @@ -14,9 +14,7 @@ if (!(canvas instanceof HTMLCanvasElement)) { window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const assets = { diff --git a/examples/src/examples/compute/edge-detect.compute-shader.wgsl b/examples/src/examples/compute/edge-detect.compute-shader.wgsl new file mode 100644 index 00000000000..ae6e6bdecb9 --- /dev/null +++ b/examples/src/examples/compute/edge-detect.compute-shader.wgsl @@ -0,0 +1,56 @@ +@group(0) @binding(0) var inputTexture: texture_2d; +@group(0) @binding(1) var inputTexture_sampler: sampler; +@group(0) @binding(2) var outputTexture: texture_storage_2d; + +@compute @workgroup_size(8, 8, 1) +fn main(@builtin(global_invocation_id) global_id : vec3u) { + let uv = vec2u(global_id.xy); + let texSize = textureDimensions(inputTexture); + + // Skip if outside texture bounds + if (uv.x >= texSize.x || uv.y >= texSize.y) { + return; + } + + // Sample the center pixel + let uvFloat = (vec2f(uv) + vec2f(0.5)) / vec2f(texSize); + var color = textureSampleLevel(inputTexture, inputTexture_sampler, uvFloat, 0.0); + + // Sobel edge detection using 3x3 kernel + let texelSize = 1.0 / vec2f(texSize); + + // Sample 3x3 neighborhood and convert to grayscale + var samples: array; + var idx = 0; + for (var y = -1; y <= 1; y++) { + for (var x = -1; x <= 1; x++) { + let offset = vec2f(f32(x), f32(y)) * texelSize; + let sampleUV = uvFloat + offset; + let sampleColor = textureSampleLevel(inputTexture, inputTexture_sampler, sampleUV, 0.0); + // Convert to grayscale using standard luminance weights + samples[idx] = dot(sampleColor.rgb, vec3f(0.299, 0.587, 0.114)); + idx++; + } + } + + // Sobel horizontal and vertical kernels + // Horizontal: [-1, 0, 1; -2, 0, 2; -1, 0, 1] + let gx = -samples[0] + samples[2] - 2.0 * samples[3] + 2.0 * samples[5] - samples[6] + samples[8]; + + // Vertical: [-1, -2, -1; 0, 0, 0; 1, 2, 1] + let gy = -samples[0] - 2.0 * samples[1] - samples[2] + samples[6] + 2.0 * samples[7] + samples[8]; + + // Calculate edge magnitude + let edgeStrength = sqrt(gx * gx + gy * gy); + + // Make edges red: stronger edges = more red + let edgeAmount = clamp(edgeStrength * 3.0, 0.0, 1.0); + let edgeColor = vec3f(1.0, 0.0, 0.0); // Red + + // Blend original color with red edges + var finalColor = mix(color.rgb, edgeColor, edgeAmount); + + // Write to output storage texture (no channel swap - keep edges red) + textureStore(outputTexture, vec2i(uv), vec4f(finalColor, color.a)); +} + diff --git a/examples/src/examples/compute/edge-detect.example.mjs b/examples/src/examples/compute/edge-detect.example.mjs new file mode 100644 index 00000000000..52fb40366fe --- /dev/null +++ b/examples/src/examples/compute/edge-detect.example.mjs @@ -0,0 +1,253 @@ +// @config DESCRIPTION A compute shader reads from a render target texture, applies edge detection and highlights edges in red. +// @config WEBGL_DISABLED +import files from 'examples/files'; +import { deviceType, rootPath } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +// set up and load draco module, as the glb we load is draco compressed +pc.WasmModule.setConfig('DracoDecoderModule', { + glueUrl: `${rootPath}/static/lib/draco/draco.wasm.js`, + wasmUrl: `${rootPath}/static/lib/draco/draco.wasm.wasm`, + fallbackUrl: `${rootPath}/static/lib/draco/draco.js` +}); + +await new Promise((resolve) => { + pc.WasmModule.getInstance('DracoDecoderModule', () => resolve()); +}); + +const assets = { + board: new pc.Asset('board', 'container', { url: `${rootPath}/static/assets/models/chess-board.glb` }), + helipad: new pc.Asset( + 'helipad-env-atlas', + 'texture', + { url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, + { type: pc.TEXTURETYPE_RGBP, mipmaps: false } + ) +}; + +const gfxOptions = { + deviceTypes: [deviceType] +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +let renderTarget = null; +let rtCamera = null; +let computeShader = null; +let compute = null; +let storageTexture = null; +let rtWidth = 0; +let rtHeight = 0; + +// Ensure canvas is resized when window changes size +const resize = () => { + app.resizeCanvas(); + + // Resize render target and storage texture to match new screen size + if (renderTarget && storageTexture) { + rtWidth = device.width; + rtHeight = Math.floor(device.height / 2); + + renderTarget.resize(rtWidth, rtHeight); + storageTexture.resize(rtWidth, rtHeight); + } +}; +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +// Create a layer for the render target +const rtLayer = new pc.Layer({ name: 'RTLayer' }); +app.scene.layers.push(rtLayer); + +// Load assets and create the scene +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + // Set up environment lighting + app.scene.envAtlas = assets.helipad.resource; + app.scene.skyboxMip = 1; + + // Create a directional light + const light = new pc.Entity('light'); + light.addComponent('light', { + type: 'directional', + color: new pc.Color(1, 1, 1), + intensity: 1 + }); + light.setEulerAngles(45, 45, 0); + app.root.addChild(light); + + // Create main camera (for final view) + const mainCamera = new pc.Entity('mainCamera'); + mainCamera.addComponent('camera', { + clearColor: new pc.Color(0.2, 0.2, 0.3) + }); + mainCamera.setPosition(0, 0, 0); + app.root.addChild(mainCamera); + + // Create the render target with MSAA support + const createRenderTarget = (useMsaa) => { + // Use screen dimensions (half height for each texture) + rtWidth = device.width; + rtHeight = Math.floor(device.height / 2); + + // Create a single-sample texture that will receive the resolved result + const texture = new pc.Texture(device, { + name: 'RT-Texture', + width: rtWidth, + height: rtHeight, + format: pc.PIXELFORMAT_RGBA8, + mipmaps: false, + minFilter: pc.FILTER_LINEAR, + magFilter: pc.FILTER_LINEAR, + addressU: pc.ADDRESS_CLAMP_TO_EDGE, + addressV: pc.ADDRESS_CLAMP_TO_EDGE + }); + + // Create render target with optional MSAA + // When samples > 1, PlayCanvas creates internal MSAA buffers and resolves to the colorBuffer + const rt = new pc.RenderTarget({ + name: 'MSAA-RT', + colorBuffer: texture, + depth: true, + samples: useMsaa ? 4 : 1 + }); + + return rt; + }; + + // Create storage texture for compute output + const createStorageTexture = () => { + return new pc.Texture(device, { + name: 'Storage-Texture', + width: rtWidth, + height: rtHeight, + format: pc.PIXELFORMAT_RGBA8, + mipmaps: false, + minFilter: pc.FILTER_LINEAR, + magFilter: pc.FILTER_LINEAR, + addressU: pc.ADDRESS_CLAMP_TO_EDGE, + addressV: pc.ADDRESS_CLAMP_TO_EDGE, + storage: true + }); + }; + + // Create the compute shader + const createComputeShader = () => { + if (!device.supportsCompute) return null; + + return new pc.Shader(device, { + name: 'EdgeDetect-Shader', + shaderLanguage: pc.SHADERLANGUAGE_WGSL, + cshader: files['compute-shader.wgsl'], + + // Format of a bind group for the compute shader + computeBindGroupFormat: new pc.BindGroupFormat(device, [ + // Input texture with sampler (sampler takes binding slot+1 automatically) + new pc.BindTextureFormat('inputTexture', pc.SHADERSTAGE_COMPUTE, undefined, undefined, true), + // Output storage texture + new pc.BindStorageTextureFormat('outputTexture', pc.PIXELFORMAT_RGBA8, pc.TEXTUREDIMENSION_2D) + ]) + }); + }; + + // Create camera that renders to the render target + let cameraAngle = 0; + const createRTCamera = (rt) => { + const cam = new pc.Entity('rtCamera'); + cam.addComponent('camera', { + clearColor: new pc.Color(1, 1, 1), + renderTarget: rt, + farClip: 500, + layers: [rtLayer.id] + }); + // Position like in multi-view example + cam.setLocalPosition(100, 35, 0); + cam.lookAt(pc.Vec3.ZERO); + app.root.addChild(cam); + return cam; + }; + + // Create the chess board entity - only render in RT layer + const boardEntity = assets.board.resource.instantiateRenderEntity({ + castShadows: true, + receiveShadows: true, + layers: [rtLayer.id] + }); + app.root.addChild(boardEntity); + + // Create the compute shader (only once) + computeShader = createComputeShader(); + + // Create resources with MSAA enabled + renderTarget = createRenderTarget(true); + rtCamera = createRTCamera(renderTarget); + storageTexture = createStorageTexture(); + + // Create compute instance if supported + if (device.supportsCompute && computeShader) { + compute = new pc.Compute(device, computeShader, 'EdgeDetect'); + + // Set up the compute parameters + // Note: sampler is automatically handled by PlayCanvas when hasSampler: true + compute.setParameter('inputTexture', renderTarget.colorBuffer); + compute.setParameter('outputTexture', storageTexture); + } + + // Update loop + let time = 0; + app.on('update', (dt) => { + time += dt; + + // Orbit camera around the scene + if (rtCamera) { + cameraAngle = time * 0.2; + rtCamera.setLocalPosition(100 * Math.sin(cameraAngle), 35, 100 * Math.cos(cameraAngle)); + rtCamera.lookAt(pc.Vec3.ZERO); + } + + if (device.supportsCompute && compute && renderTarget) { + // Set up dispatch dimensions (workgroup size is 8x8 in shader) + const workgroupsX = Math.ceil(rtWidth / 8); + const workgroupsY = Math.ceil(rtHeight / 8); + compute.setupDispatch(workgroupsX, workgroupsY, 1); + + // Dispatch the compute shader + device.computeDispatch([compute], 'EdgeDetect-Dispatch'); + + const gap = 0.02; + + // Top half: original RT texture + app.drawTexture(0, 0.5 - gap * 0.5, 2.0 - gap * 2, 1.0 - gap * 2, renderTarget.colorBuffer); + + // Bottom half: compute-processed texture + app.drawTexture(0, -0.5 + gap * 0.5, 2.0 - gap * 2, 1.0 - gap * 2, storageTexture); + } + }); +}); + +export { app }; diff --git a/examples/src/examples/compute/histogram.example.mjs b/examples/src/examples/compute/histogram.example.mjs index cfcb8653551..454049e7398 100644 --- a/examples/src/examples/compute/histogram.example.mjs +++ b/examples/src/examples/compute/histogram.example.mjs @@ -21,9 +21,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/compute/particles.example.mjs b/examples/src/examples/compute/particles.example.mjs index a711f84d95a..57c85536a20 100644 --- a/examples/src/examples/compute/particles.example.mjs +++ b/examples/src/examples/compute/particles.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/compute/texture-gen.example.mjs b/examples/src/examples/compute/texture-gen.example.mjs index 543b0975aee..55d2ee6b9ef 100644 --- a/examples/src/examples/compute/texture-gen.example.mjs +++ b/examples/src/examples/compute/texture-gen.example.mjs @@ -18,9 +18,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/compute/vertex-update.example.mjs b/examples/src/examples/compute/vertex-update.example.mjs index 5ac6d33a29c..332fcd86090 100644 --- a/examples/src/examples/compute/vertex-update.example.mjs +++ b/examples/src/examples/compute/vertex-update.example.mjs @@ -20,9 +20,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/gaussian-splatting/crop.controls.mjs b/examples/src/examples/gaussian-splatting/crop.controls.mjs new file mode 100644 index 00000000000..338b2bbd811 --- /dev/null +++ b/examples/src/examples/gaussian-splatting/crop.controls.mjs @@ -0,0 +1,36 @@ +/** + * @param {import('../../app/components/Example.mjs').ControlOptions} options - The options. + * @returns {JSX.Element} The returned JSX Element. + */ +export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { + const { BindingTwoWay, LabelGroup, BooleanInput, Button, SliderInput } = ReactPCUI; + + return fragment( + jsx( + LabelGroup, + { text: 'Precise' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'precise' } + }) + ), + jsx( + LabelGroup, + { text: 'Edge Scale' }, + jsx(SliderInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'edgeScale' }, + min: 0.1, + max: 1.0, + precision: 2 + }) + ), + jsx(Button, { + text: 'Pause / Play', + onClick: () => { + observer.emit('togglePause'); + } + }) + ); +}; diff --git a/examples/src/examples/gaussian-splatting/crop.example.mjs b/examples/src/examples/gaussian-splatting/crop.example.mjs new file mode 100644 index 00000000000..dfc2e91f97e --- /dev/null +++ b/examples/src/examples/gaussian-splatting/crop.example.mjs @@ -0,0 +1,226 @@ +// @config DESCRIPTION This example demonstrates AABB-based cropping of gaussian splats with animated bounds. +import { data } from 'examples/observer'; +import { deviceType, rootPath, fileImport } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const { GsplatCropShaderEffect } = await fileImport(`${rootPath}/static/scripts/esm/gsplat/shader-effect-crop.mjs`); + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +const gfxOptions = { + deviceTypes: [deviceType], + + // disable antialiasing as gaussian splats do not benefit from it and it's expensive + antialias: false +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.mouse = new pc.Mouse(document.body); +createOptions.touch = new pc.TouchDevice(document.body); + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem, + pc.GSplatComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler, pc.GSplatHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +const assets = { + hotel: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/hotel-culpture.compressed.ply` }), + orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }) +}; + +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + // Default precise mode to true, paused to false, edge scale to 0.5 + data.set('precise', true); + data.set('edgeScale', 0.5); + let paused = false; + + // Handle pause/play toggle + data.on('togglePause', () => { + paused = !paused; + }); + + // Create hotel gsplat with unified set to true + const hotel = new pc.Entity('hotel'); + hotel.addComponent('gsplat', { + asset: assets.hotel, + unified: true + }); + hotel.setLocalEulerAngles(180, 0, 0); + app.root.addChild(hotel); + + // Add script component to the hotel entity + hotel.addComponent('script'); + + // Create the crop effect script + const cropScript = hotel.script?.create(GsplatCropShaderEffect); + + // Set initial edge scale factor + if (cropScript) { + cropScript.edgeScaleFactor = data.get('edgeScale'); + } + + // Handle edge scale changes + data.on('edgeScale:set', () => { + if (cropScript) { + cropScript.edgeScaleFactor = data.get('edgeScale'); + } + }); + + // Get the gsplat material + const getMaterial = () => app.scene.gsplat?.material; + + // Set initial define state + /** + * @param {boolean} precise - Whether to enable precise cropping + */ + const updatePreciseDefine = (precise) => { + const material = getMaterial(); + if (material) { + if (precise) { + material.setDefine('GSPLAT_PRECISE_CROP', ''); + } else { + material.defines.delete('GSPLAT_PRECISE_CROP'); + } + material.update(); + } + }; + + // Wait for material to be available, then set initial state + const checkMaterial = () => { + const material = getMaterial(); + if (material) { + updatePreciseDefine(data.get('precise')); + } else { + setTimeout(checkMaterial, 100); + } + }; + checkMaterial(); + + // Handle precise toggle changes + data.on('precise:set', () => { + updatePreciseDefine(data.get('precise')); + }); + + // Create an Entity with a camera component + const camera = new pc.Entity(); + camera.addComponent('camera', { + clearColor: pc.Color.BLACK, + fov: 80 + }); + camera.setLocalPosition(3, 1, 0.5); + + // add orbit camera script with a mouse and a touch support + camera.addComponent('script'); + camera.script?.create('orbitCamera', { + attributes: { + inertiaFactor: 0.2, + focusEntity: hotel, + distanceMax: 2, + frameOnStart: false + } + }); + camera.script?.create('orbitCameraInputMouse'); + camera.script?.create('orbitCameraInputTouch'); + app.root.addChild(camera); + + // Setup bloom post-processing + if (camera.camera) { + const cameraFrame = new pc.CameraFrame(app, camera.camera); + cameraFrame.rendering.samples = 4; + cameraFrame.rendering.toneMapping = pc.TONEMAP_ACES; + cameraFrame.bloom.intensity = 0.03; + cameraFrame.bloom.blurLevel = 6; + cameraFrame.update(); + } + + // Auto-rotate camera when idle + let autoRotateEnabled = true; + let lastInteractionTime = 0; + const autoRotateDelay = 2; // seconds of inactivity before auto-rotate resumes + const autoRotateSpeed = 10; // degrees per second + + // Detect user interaction (click/touch only, not mouse movement) + const onUserInteraction = () => { + autoRotateEnabled = false; + lastInteractionTime = Date.now(); + }; + + // Listen for click and touch events only + if (app.mouse) { + app.mouse.on('mousedown', onUserInteraction); + app.mouse.on('mousewheel', onUserInteraction); + } + if (app.touch) { + app.touch.on('touchstart', onUserInteraction); + } + + // Clean up event listeners on destroy + app.on('destroy', () => { + if (app.mouse) { + app.mouse.off('mousedown', onUserInteraction); + app.mouse.off('mousewheel', onUserInteraction); + } + if (app.touch) { + app.touch.off('touchstart', onUserInteraction); + } + }); + + // Animate AABB size with soft bounce + const period = 9.0; // seconds for one cycle + const minSize = 0.4; + const maxSize = 1.75; + let elapsedTime = 0; + + app.on('update', (dt) => { + // Re-enable auto-rotate after delay + if (!autoRotateEnabled && (Date.now() - lastInteractionTime) / 1000 > autoRotateDelay) { + autoRotateEnabled = true; + } + + // Apply auto-rotation + if (autoRotateEnabled) { + const orbitCamera = camera.script?.get('orbitCamera'); + if (orbitCamera) { + orbitCamera.yaw += autoRotateSpeed * dt; + } + } + + // Animate AABB with soft bounce (sin-based easing) + if (cropScript && !paused) { + elapsedTime += dt; + const t = (Math.sin(elapsedTime * Math.PI * 2 / period) + 1) / 2; // 0 to 1, soft bounce + const size = minSize + t * (maxSize - minSize); + const sizeXZ = size * 1.5; // 50% wider in X and Z directions + cropScript.aabbMin.set(-sizeXZ, -size, -sizeXZ); + cropScript.aabbMax.set(sizeXZ, size, sizeXZ); + } + }); +}); + +export { app }; diff --git a/examples/src/examples/gaussian-splatting/flipbook.example.mjs b/examples/src/examples/gaussian-splatting/flipbook.example.mjs new file mode 100644 index 00000000000..b2f57d845db --- /dev/null +++ b/examples/src/examples/gaussian-splatting/flipbook.example.mjs @@ -0,0 +1,175 @@ +// @config DESCRIPTION This example demonstrates gsplat flipbook animation using dynamically loaded splat sequences at different playback speeds. +// @config HIDDEN +// @config NO_MINISTATS +import { deviceType, rootPath, fileImport } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const { GsplatFlipbook } = await fileImport(`${rootPath}/static/scripts/esm/gsplat/gsplat-flipbook.mjs`); + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +const gfxOptions = { + deviceTypes: [deviceType], + glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, + twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, + + // disable antialiasing as gaussian splats do not benefit from it and it's expensive + antialias: false +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.mouse = new pc.Mouse(document.body); +createOptions.touch = new pc.TouchDevice(document.body); + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem, + pc.GSplatComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler, pc.GSplatHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +// Create assets for scripts and skydome +const assets = { + helipad: new pc.Asset( + 'helipad-env-atlas', + 'texture', + { url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, + { type: pc.TEXTURETYPE_RGBP, mipmaps: false } + ), + orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }) +}; + +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + // setup skydome + app.scene.skyboxMip = 2; + app.scene.exposure = 0.2; + app.scene.envAtlas = assets.helipad.resource; + + // Mini-Stats: add VRAM on top of default stats + const msOptions = pc.MiniStats.getDefaultOptions(); + msOptions.stats.push({ + name: 'VRAM', + stats: ['vram.tex'], + decimalPlaces: 1, + multiplier: 1 / (1024 * 1024), + unitsName: 'MB', + watermark: 1024 + }); + const miniStats = new pc.MiniStats(app, msOptions); // eslint-disable-line no-unused-vars + + // Create an Entity with a camera component + const camera = new pc.Entity(); + camera.addComponent('camera', { + clearColor: new pc.Color(0.2, 0.2, 0.2), + toneMapping: pc.TONEMAP_ACES + }); + camera.setLocalPosition(12.67, 1.16, -1.48); + + // add orbit camera script with a mouse and a touch support + camera.addComponent('script'); + camera.script.create('orbitCamera', { + attributes: { + inertiaFactor: 0.2, + distanceMax: 100, + frameOnStart: false + } + }); + camera.script.create('orbitCameraInputMouse'); + camera.script.create('orbitCameraInputTouch'); + app.root.addChild(camera); + + // Helper function to create a flipbook entity + const createFlipbookEntity = (name, fps, folder, filenamePattern, startFrame, endFrame, position, rotation, scale) => { + const entity = new pc.Entity(name); + entity.addComponent('gsplat', { + unified: true + }); + entity.addComponent('script'); + const flipbook = entity.script.create(GsplatFlipbook); + if (flipbook) { + flipbook.fps = fps; + flipbook.folder = folder; + flipbook.filenamePattern = filenamePattern; + flipbook.startFrame = startFrame; + flipbook.endFrame = endFrame; + flipbook.playMode = 'bounce'; + flipbook.playing = true; + } + entity.setLocalPosition(position[0], position[1], position[2]); + entity.setLocalEulerAngles(rotation[0], rotation[1], rotation[2]); + entity.setLocalScale(scale, scale, scale); + app.root.addChild(entity); + return entity; + }; + + // Create monkey flipbook at origin (camera focus) + const monkey = createFlipbookEntity( + 'Monkey', + 30, + `${rootPath}/static/assets/splats/flipbook`, + 'monkey_{frame:04}.sog', + 1, + 50, + [0, 0, 0], + [180, 90, 0], + 0.7 + ); + + // Create first wave entity at 60 fps + createFlipbookEntity( + 'Wave 1 (60fps)', + 60, + `${rootPath}/static/assets/splats/wave`, + 'wave_{frame:04}.sog', + 1, + 200, + [0, -7, 0], + [180, 90, 0], + 5 + ); + + // Create second wave entity at 30 fps (flipped upside down) + createFlipbookEntity( + 'Wave 2 (30fps)', + 30, + `${rootPath}/static/assets/splats/wave`, + 'wave_{frame:04}.sog', + 1, + 200, + [0, 7, 0], + [0, 90, 0], + 5 + ); + + // Update camera focus to monkey entity + const orbitCamera = camera.script?.get('orbitCamera'); + if (orbitCamera) { + orbitCamera.focusEntity = monkey; + } +}); + +export { app }; diff --git a/examples/src/examples/gaussian-splatting/global-sorting.example.mjs b/examples/src/examples/gaussian-splatting/global-sorting.example.mjs index 1678110a142..b6bfb88ce92 100644 --- a/examples/src/examples/gaussian-splatting/global-sorting.example.mjs +++ b/examples/src/examples/gaussian-splatting/global-sorting.example.mjs @@ -8,8 +8,6 @@ window.focus(); const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable antialiasing as gaussian splats do not benefit from it and it's expensive antialias: false diff --git a/examples/src/examples/gaussian-splatting/lod-streaming.controls.mjs b/examples/src/examples/gaussian-splatting/lod-streaming.controls.mjs index dda5e192bc0..a09c0962c9d 100644 --- a/examples/src/examples/gaussian-splatting/lod-streaming.controls.mjs +++ b/examples/src/examples/gaussian-splatting/lod-streaming.controls.mjs @@ -43,6 +43,24 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { { v: 'mobile', t: 'Mobile (3-5)' } ] }) + ), + jsx( + LabelGroup, + { text: 'Splat Budget' }, + jsx(SelectInput, { + type: 'string', + binding: new BindingTwoWay(), + link: { observer, path: 'splatBudget' }, + value: observer.get('splatBudget') || '4M', + options: [ + { v: 'none', t: 'No limit' }, + { v: '1M', t: '1M' }, + { v: '2M', t: '2M' }, + { v: '3M', t: '3M' }, + { v: '4M', t: '4M' }, + { v: '6M', t: '6M' } + ] + }) ) ), jsx( diff --git a/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs b/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs index ba2dc732675..328c42f6ac6 100644 --- a/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs +++ b/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs @@ -1,3 +1,4 @@ +// @config NO_MINISTATS import { data } from 'examples/observer'; import { deviceType, rootPath, fileImport } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -114,7 +115,7 @@ assetListLoader.load(() => { app.scene.skyboxMip = 1; app.scene.exposure = 1.5; - // Mini-Stats: add VRAM on top of default stats + // Mini-Stats: add VRAM and gsplats on top of default stats const msOptions = pc.MiniStats.getDefaultOptions(); msOptions.stats.push({ name: 'VRAM', @@ -124,6 +125,14 @@ assetListLoader.load(() => { unitsName: 'MB', watermark: 1024 }); + msOptions.stats.push({ + name: 'GSplats', + stats: ['frame.gsplats'], + decimalPlaces: 3, + multiplier: 1 / 1000000, + unitsName: 'M', + watermark: 10 + }); const miniStats = new pc.MiniStats(app, msOptions); // eslint-disable-line no-unused-vars // enable rotation-based LOD updates and behind-camera penalty @@ -136,6 +145,7 @@ assetListLoader.load(() => { // initialize UI settings data.set('debugLod', false); data.set('lodPreset', pc.platform.mobile ? 'mobile' : 'desktop'); + data.set('splatBudget', pc.platform.mobile ? '1M' : '4M'); app.scene.gsplat.colorizeLod = !!data.get('debugLod'); @@ -166,12 +176,28 @@ assetListLoader.load(() => { applyPreset(); data.on('lodPreset:set', applyPreset); + const applySplatBudget = () => { + const preset = data.get('splatBudget'); + const budgetMap = { + 'none': 0, + '1M': 1000000, + '2M': 2000000, + '3M': 3000000, + '4M': 4000000, + '6M': 6000000 + }; + gs.splatBudget = budgetMap[preset] || 0; + }; + + applySplatBudget(); + data.on('splatBudget:set', applySplatBudget); + // Create a camera with fly controls const camera = new pc.Entity('camera'); camera.addComponent('camera', { clearColor: new pc.Color(0.2, 0.2, 0.2), fov: 75, - toneMapping: pc.TONEMAP_ACES + toneMapping: pc.TONEMAP_LINEAR }); // Set camera position diff --git a/examples/src/examples/gaussian-splatting/lod.controls.mjs b/examples/src/examples/gaussian-splatting/lod.controls.mjs deleted file mode 100644 index c8fec77edbb..00000000000 --- a/examples/src/examples/gaussian-splatting/lod.controls.mjs +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @param {import('../../app/components/Example.mjs').ControlOptions} options - The options. - * @returns {JSX.Element} The returned JSX Element. - */ -export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { - const { BindingTwoWay, LabelGroup, BooleanInput, SliderInput, Panel } = ReactPCUI; - if (!observer.get('lod')) { - observer.set('lod', { distance: 5 }); - } - return fragment( - jsx( - Panel, - { headerText: 'Debug' }, - jsx( - LabelGroup, - { text: 'AABBs' }, - jsx(BooleanInput, { - type: 'toggle', - binding: new BindingTwoWay(), - link: { observer, path: 'debugAabbs' }, - value: observer.get('debugAabbs') - }) - ), - jsx( - LabelGroup, - { text: 'LOD' }, - jsx(BooleanInput, { - type: 'toggle', - binding: new BindingTwoWay(), - link: { observer, path: 'debugLod' }, - value: observer.get('debugLod') - }) - ) - ), - jsx( - Panel, - { headerText: 'LOD' }, - jsx( - LabelGroup, - { text: 'Distance' }, - jsx(SliderInput, { - precision: 1, - min: 3, - max: 20, - step: 0.1, - binding: new BindingTwoWay(), - link: { observer, path: 'lod.distance' } - }) - ) - ) - ); -}; diff --git a/examples/src/examples/gaussian-splatting/lod.example.mjs b/examples/src/examples/gaussian-splatting/lod.example.mjs deleted file mode 100644 index fcc6b4c7c20..00000000000 --- a/examples/src/examples/gaussian-splatting/lod.example.mjs +++ /dev/null @@ -1,192 +0,0 @@ -// @config HIDDEN -import { data } from 'examples/observer'; -import { deviceType, rootPath, fileImport } from 'examples/utils'; -import * as pc from 'playcanvas'; - -const { CameraControls } = await fileImport(`${rootPath}/static/scripts/esm/camera-controls.mjs`); - -const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); -window.focus(); - -const gfxOptions = { - deviceTypes: [deviceType], - - // disable antialiasing as gaussian splats do not benefit from it and it's expensive - antialias: false -}; - -const device = await pc.createGraphicsDevice(canvas, gfxOptions); -device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); - -const createOptions = new pc.AppOptions(); -createOptions.graphicsDevice = device; -createOptions.mouse = new pc.Mouse(document.body); -createOptions.touch = new pc.TouchDevice(document.body); -createOptions.keyboard = new pc.Keyboard(document.body); - -createOptions.componentSystems = [ - pc.RenderComponentSystem, - pc.CameraComponentSystem, - pc.LightComponentSystem, - pc.ScriptComponentSystem, - pc.GSplatComponentSystem -]; -createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler, pc.GSplatHandler]; - -const app = new pc.AppBase(canvas); -app.init(createOptions); - -// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size -app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); -app.setCanvasResolution(pc.RESOLUTION_AUTO); - -// Ensure canvas is resized when window changes size -const resize = () => app.resizeCanvas(); -window.addEventListener('resize', resize); -app.on('destroy', () => { - window.removeEventListener('resize', resize); -}); - -// pc.Tracing.set(pc.TRACEID_SHADER_ALLOC, true); -// pc.Tracing.set(pc.TRACEID_OCTREE_RESOURCES, true); - -const assets = { - // church: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/morocco.ply` }), - church: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/morocco/lod-meta.json` }), - // church: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/garage/lod-meta.json` }), - logo: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/lod/lod-meta.json` }), - guitar: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/guitar.compressed.ply` }), - skull: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/skull.sog` }) -}; - -const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); -assetListLoader.load(() => { - app.start(); - - // initialize UI settings - data.set('debugAabbs', !!data.get('debugAabbs')); - data.set('debugLod', !!data.get('debugLod')); - if (!data.get('lod')) data.set('lod', { distance: 5 }); - app.scene.gsplat.debugAabbs = !!data.get('debugAabbs'); - app.scene.gsplat.colorizeLod = !!data.get('debugLod'); - - // handle UI changes - data.on('debugAabbs:set', () => { - app.scene.gsplat.debugAabbs = !!data.get('debugAabbs'); - }); - data.on('debugLod:set', () => { - app.scene.gsplat.colorizeLod = !!data.get('debugLod'); - }); - - // create a splat entity and place it in the world - const skull = new pc.Entity('skull'); - skull.addComponent('gsplat', { - asset: assets.skull, - unified: true - }); - skull.setLocalPosition(2.5, 1, 1); - skull.setLocalEulerAngles(180, 90, 0); - skull.setLocalScale(0.7, 0.7, 0.7); - app.root.addChild(skull); - - // create a splat entity and place it in the world - const logo = new pc.Entity('logo'); - logo.addComponent('gsplat', { - asset: assets.logo, - unified: true - }); - app.root.addChild(logo); - logo.setLocalPosition(0, 1.5, 1); - logo.setLocalEulerAngles(180, 0, 0); - logo.setLocalScale(0.5, 0.5, 0.5); - - // create a splat entity and place it in the world - const church = new pc.Entity('church'); - church.addComponent('gsplat', { - asset: assets.church, - unified: true - }); - app.root.addChild(church); - church.setLocalEulerAngles(180, 90, 0); - - const guitar = new pc.Entity('guitar'); - guitar.addComponent('gsplat', { - asset: assets.guitar, - unified: true - }); - app.root.addChild(guitar); - guitar.setLocalPosition(0, 0.6, 4); - guitar.setLocalEulerAngles(180, 0, 0); - guitar.setLocalScale(0.5, 0.5, 0.5); - - // Create an Entity with a camera component - const camera = new pc.Entity(); - camera.addComponent('camera', { - clearColor: new pc.Color(0.2, 0.2, 0.2), - fov: 75, - toneMapping: pc.TONEMAP_ACES - }); - camera.setLocalPosition(-0.8, 2, 3); - camera.lookAt(2, 2, 0); - app.root.addChild(camera); - - camera.addComponent('script'); - const cc = /** @type { CameraControls} */ (camera.script.create(CameraControls)); - const sceneSize = 500; - Object.assign(cc, { - moveSpeed: 0.05 * sceneSize, - moveFastSpeed: 0.3 * sceneSize, - // moveSpeed: 0.005 * sceneSize, - // moveFastSpeed: 0.03 * sceneSize, - enableOrbit: false, - enablePan: false - }); - - // bind LOD distance slider to component lodDistances for church and logo - /** @returns {void} */ - const updateLodDistances = () => { - const base = Number(data.get('lod.distance')) || 5; - const distances = [base, base * 2, base * 3, base * 4, base * 5]; - /** @type {any} */ (logo.gsplat).lodDistances = distances; - /** @type {any} */ (church.gsplat).lodDistances = distances; - }; - updateLodDistances(); - data.on('lod.distance:set', updateLodDistances); - - let timeToChange = 3; - let time = 0; - let guitarTime = 0; - let added = true; - app.on('update', (/** @type {number} */ dt) => { - time += dt; - timeToChange -= dt; - - // ping pong logo between two positions along x-axies - logo.setLocalPosition(5.5 + 5 * Math.sin(time), 1.5, -2); - logo.rotateLocal(0, 100 * dt, 0); - - // update the guitar as well - guitarTime += dt; - guitar.setLocalPosition(0.5 * Math.sin(guitarTime), 2, 0.5 * Math.cos(guitarTime) + 1); - - if (timeToChange <= 0) { - - if (!added) { - // console.log('adding skull'); - added = true; - timeToChange = 3; - - skull.enabled = true; - - } else { - // console.log('removing skull'); - added = false; - timeToChange = 3; - - skull.enabled = false; - } - } - }); -}); - -export { app }; diff --git a/examples/src/examples/gaussian-splatting/multi-splat.example.mjs b/examples/src/examples/gaussian-splatting/multi-splat.example.mjs index a9db2259b2a..b36a6cbf8fc 100644 --- a/examples/src/examples/gaussian-splatting/multi-splat.example.mjs +++ b/examples/src/examples/gaussian-splatting/multi-splat.example.mjs @@ -75,7 +75,7 @@ assetListLoader.load(() => { asset: assets.guitar }); const customShaderFile = shaderLanguage === 'wgsl' ? 'shader.wgsl.vert' : 'shader.glsl.vert'; - guitar.gsplat.material.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', files[customShaderFile]); + guitar.gsplat.material.getShaderChunks(shaderLanguage).set('gsplatModifyVS', files[customShaderFile]); guitar.setLocalPosition(0, 0.8, 0); guitar.setLocalEulerAngles(0, 0, 180); guitar.setLocalScale(0.4, 0.4, 0.4); @@ -125,17 +125,17 @@ assetListLoader.load(() => { const mat1 = biker.gsplat.material; if (useCustomShader) { - mat1.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', vs); + mat1.getShaderChunks(shaderLanguage).set('gsplatModifyVS', vs); } else { - mat1.getShaderChunks(shaderLanguage).delete('gsplatCustomizeVS'); + mat1.getShaderChunks(shaderLanguage).delete('gsplatModifyVS'); } mat1.update(); const mat2 = skull.gsplat.material; if (useCustomShader) { - mat2.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', vs); + mat2.getShaderChunks(shaderLanguage).set('gsplatModifyVS', vs); } else { - mat2.getShaderChunks(shaderLanguage).delete('gsplatCustomizeVS'); + mat2.getShaderChunks(shaderLanguage).delete('gsplatModifyVS'); } mat2.setDefine('CUTOUT', true); mat2.update(); diff --git a/examples/src/examples/gaussian-splatting/multi-splat.shader.glsl.vert b/examples/src/examples/gaussian-splatting/multi-splat.shader.glsl.vert index a271f1d178f..1ce2bf5ad87 100644 --- a/examples/src/examples/gaussian-splatting/multi-splat.shader.glsl.vert +++ b/examples/src/examples/gaussian-splatting/multi-splat.shader.glsl.vert @@ -1,16 +1,16 @@ uniform float uTime; -void modifyCenter(inout vec3 center) { +void modifySplatCenter(inout vec3 center) { // modify center float heightIntensity = center.y * 0.2; center.x += sin(uTime * 5.0 + center.y) * 0.3 * heightIntensity; } -void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { // no modification } -void modifyColor(vec3 center, inout vec4 clr) { +void modifySplatColor(vec3 center, inout vec4 clr) { float sineValue = abs(sin(uTime * 5.0 + center.y)); #ifdef CUTOUT @@ -25,4 +25,3 @@ void modifyColor(vec3 center, inout vec4 clr) { clr.xyz = mix(clr.xyz, gold, blend); #endif } - diff --git a/examples/src/examples/gaussian-splatting/multi-splat.shader.wgsl.vert b/examples/src/examples/gaussian-splatting/multi-splat.shader.wgsl.vert index 4c10889d034..fd04981623b 100644 --- a/examples/src/examples/gaussian-splatting/multi-splat.shader.wgsl.vert +++ b/examples/src/examples/gaussian-splatting/multi-splat.shader.wgsl.vert @@ -1,16 +1,16 @@ uniform uTime: f32; -fn modifyCenter(center: ptr) { +fn modifySplatCenter(center: ptr) { // modify center let heightIntensity = (*center).y * 0.2; (*center).x += sin(uniform.uTime * 5.0 + (*center).y) * 0.3 * heightIntensity; } -fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr, covB: ptr) { +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { // no modification } -fn modifyColor(center: vec3f, clr: ptr) { +fn modifySplatColor(center: vec3f, clr: ptr) { let sineValue = abs(sin(uniform.uTime * 5.0 + center.y)); #ifdef CUTOUT @@ -25,4 +25,3 @@ fn modifyColor(center: vec3f, clr: ptr) { (*clr) = vec4f(mix((*clr).xyz, gold, blend), (*clr).a); #endif } - diff --git a/examples/src/examples/gaussian-splatting/picking.example.mjs b/examples/src/examples/gaussian-splatting/picking.example.mjs index fb44be2b3e6..4932c97543f 100644 --- a/examples/src/examples/gaussian-splatting/picking.example.mjs +++ b/examples/src/examples/gaussian-splatting/picking.example.mjs @@ -1,5 +1,4 @@ // @config DESCRIPTION This example shows how to use the Picker to pick GSplat objects in the scene. -import files from 'examples/files'; import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -15,8 +14,6 @@ const gfxOptions = { const device = await pc.createGraphicsDevice(canvas, gfxOptions); device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); -const shaderLanguage = device.isWebGPU ? 'wgsl' : 'glsl'; - const createOptions = new pc.AppOptions(); createOptions.graphicsDevice = device; createOptions.mouse = new pc.Mouse(document.body); @@ -63,7 +60,7 @@ assetListLoader.load(() => { // setup skydome app.scene.skyboxMip = 3; app.scene.envAtlas = assets.helipad.resource; - app.scene.skyboxIntensity = 0.3; + app.scene.skyboxIntensity = 0.1; // create multiple instances of the gsplat const entities = []; @@ -78,13 +75,6 @@ assetListLoader.load(() => { app.root.addChild(splat); - // specify custom vertex shader - const customShaderFile = shaderLanguage === 'wgsl' ? 'shader.wgsl.vert' : 'shader.glsl.vert'; - splat.gsplat.material.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', files[customShaderFile]); - - // set alpha clip value, used picking - splat.gsplat.material.setParameter('alphaClip', 0.4); - entities.push({ entity: splat, fade: 0 @@ -121,8 +111,8 @@ assetListLoader.load(() => { cameraFrame.bloom.intensity = 0.01; cameraFrame.update(); - // Create an instance of the picker class - const picker = new pc.Picker(app, 1, 1); + // Create an instance of the picker class with depth enabled + const picker = new pc.Picker(app, 1, 1, true); // update things each frame let time = 0; @@ -137,9 +127,28 @@ assetListLoader.load(() => { entity.entity.setLocalPosition(6 * Math.sin(offset2pi), 0, 6 * Math.cos(offset2pi)); entity.entity.rotate(0, 150 * fraction * dt, 0); - // update face value and supply it to material as uniform + // update fade value entity.fade = Math.max(entity.fade - 0.5 * dt, 0); - entity.entity.gsplat.material.setParameter('fade', entity.fade); + + // calculate scale animation based on fade + const angle = entity.fade * Math.PI; + const shrinkFactor = Math.sin(angle) * 0.5; + const scale = 1.0 - shrinkFactor; + + // apply scale to the entity transform so both the splat and marker spheres scale together + entity.entity.setLocalScale(scale, scale, scale); + } + + // display the picker's buffers side by side in the bottom right corner + // color buffer (left) and depth buffer (right), with equal margins from edges + if (picker.colorBuffer) { + // @ts-ignore engine-tsd + app.drawTexture(0.55, -0.77, 0.2, 0.2, picker.colorBuffer); + } + + if (picker.depthBuffer) { + // @ts-ignore engine-tsd + app.drawTexture(0.77, -0.77, 0.2, 0.2, picker.depthBuffer); } }); @@ -154,17 +163,43 @@ assetListLoader.load(() => { const worldLayer = app.scene.layers.getLayerByName('World'); picker.prepare(camera.camera, app.scene, [worldLayer]); - // get the meshInstance of the picked object - picker.getSelectionAsync(x * pickerScale, y * pickerScale, 1, 1).then((meshInstances) => { - - if (meshInstances.length > 0) { - const meshInstance = meshInstances[0]; - // find entity with matching mesh instance - const entity = entities.find(e => e.entity.gsplat.instance.meshInstance === meshInstance); - if (entity) { - // trigger the visual effect - entity.fade = 1; - } + // get the world position at the clicked point + picker.getWorldPointAsync(x * pickerScale, y * pickerScale).then((worldPoint) => { + if (worldPoint) { + // get the meshInstance of the picked object + picker.getSelectionAsync(x * pickerScale, y * pickerScale, 1, 1).then((meshInstances) => { + + if (meshInstances.length > 0) { + const meshInstance = meshInstances[0]; + // find entity with matching mesh instance + const entity = entities.find(e => e.entity.gsplat.instance.meshInstance === meshInstance); + if (entity) { + // trigger the visual effect only if not already animating + if (entity.fade === 0) { + entity.fade = 1; + } + + // create a new marker sphere at the picked point with random color + const markerMaterial = new pc.StandardMaterial(); + markerMaterial.emissive = new pc.Color(Math.random(), Math.random(), Math.random()); + markerMaterial.emissiveIntensity = 300; + markerMaterial.useLighting = false; + markerMaterial.update(); + + const markerSphere = new pc.Entity('marker'); + markerSphere.addComponent('render', { + type: 'sphere', + material: markerMaterial + }); + markerSphere.setLocalScale(0.3, 0.3, 0.3); + + // parent it to the picked entity and convert world position to its local space + entity.entity.addChild(markerSphere); + const localPos = entity.entity.getWorldTransform().clone().invert().transformPoint(worldPoint); + markerSphere.setLocalPosition(localPos); + } + } + }); } }); }; diff --git a/examples/src/examples/gaussian-splatting/picking.shader.glsl.vert b/examples/src/examples/gaussian-splatting/picking.shader.glsl.vert deleted file mode 100644 index 0f2c80915bb..00000000000 --- a/examples/src/examples/gaussian-splatting/picking.shader.glsl.vert +++ /dev/null @@ -1,37 +0,0 @@ -uniform float fade; - -// animate center position based on the fade value -void modifyCenter(inout vec3 pos) { - // Use a sine wave to create a smooth scale down and back up animation - float angle = fade * 3.14159265; - float shrinkFactor = sin(angle) * 0.3; - float scale = 1.0 - shrinkFactor; - pos *= scale; -} - -void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { - // no modification -} - -// animate color based on the fade value -void modifyColor(vec3 center, inout vec4 clr) { - - // Check if the color is approximately grayscale - float r = clr.r; - float g = clr.g; - float b = clr.b; - - float grayscaleThreshold = 0.01; - bool isGrayscale = abs(r - g) < grayscaleThreshold && - abs(r - b) < grayscaleThreshold && - abs(g - b) < grayscaleThreshold; - - if (isGrayscale) { - // If the color is grayscale, make it very bright (the PC logo) - clr.rgb *= 10.0; - } else { - // cross fade blue to original orange color based on fade value - clr.rgb = mix(clr.bgr * 0.5, clr.rgb, fade); - } -} - diff --git a/examples/src/examples/gaussian-splatting/picking.shader.wgsl.vert b/examples/src/examples/gaussian-splatting/picking.shader.wgsl.vert deleted file mode 100644 index 782fe7ae48c..00000000000 --- a/examples/src/examples/gaussian-splatting/picking.shader.wgsl.vert +++ /dev/null @@ -1,37 +0,0 @@ -uniform fade: f32; - -// animate center position based on the fade value -fn modifyCenter(pos: ptr) { - // Use a sine wave to create a smooth scale down and back up animation - let angle = uniform.fade * 3.14159265; - let shrinkFactor = sin(angle) * 0.3; - let scale = 1.0 - shrinkFactor; - *pos = *pos * scale; -} - -fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr, covB: ptr) { - // no modification -} - -// animate color based on the fade value -fn modifyColor(center: vec3f, clr: ptr) { - - // Check if the color is approximately grayscale - let r = (*clr).r; - let g = (*clr).g; - let b = (*clr).b; - - let grayscaleThreshold = 0.01; - let isGrayscale = abs(r - g) < grayscaleThreshold && - abs(r - b) < grayscaleThreshold && - abs(g - b) < grayscaleThreshold; - - if (isGrayscale) { - // If the color is grayscale, make it very bright (the PC logo) - *clr = vec4f((*clr).rgb * 10.0, (*clr).a); - } else { - // cross fade blue to original orange color based on fade value - *clr = vec4f(mix((*clr).bgr * 0.5, (*clr).rgb, uniform.fade), (*clr).a); - } -} - diff --git a/examples/src/examples/gaussian-splatting/reveal.example.mjs b/examples/src/examples/gaussian-splatting/reveal.example.mjs index 1220431ae9f..625b620e78a 100644 --- a/examples/src/examples/gaussian-splatting/reveal.example.mjs +++ b/examples/src/examples/gaussian-splatting/reveal.example.mjs @@ -12,8 +12,6 @@ window.focus(); const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable antialiasing as gaussian splats do not benefit from it and it's expensive antialias: false diff --git a/examples/src/examples/gaussian-splatting/shader-effects.example.mjs b/examples/src/examples/gaussian-splatting/shader-effects.example.mjs index 1f83fbcd68b..47935c1dfe5 100644 --- a/examples/src/examples/gaussian-splatting/shader-effects.example.mjs +++ b/examples/src/examples/gaussian-splatting/shader-effects.example.mjs @@ -10,8 +10,6 @@ window.focus(); const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable antialiasing as gaussian splats do not benefit from it and it's expensive antialias: false @@ -280,16 +278,6 @@ assetListLoader.load(() => { camera.script?.create('orbitCameraInputTouch'); app.root.addChild(camera); - // Setup bloom post-processing - if (camera.camera) { - const cameraFrame = new pc.CameraFrame(app, camera.camera); - cameraFrame.rendering.samples = 4; - cameraFrame.rendering.toneMapping = pc.TONEMAP_ACES; - cameraFrame.bloom.intensity = 0.03; - cameraFrame.bloom.blurLevel = 6; - cameraFrame.update(); - } - // Auto-rotate camera when idle let autoRotateEnabled = true; let lastInteractionTime = 0; diff --git a/examples/src/examples/gaussian-splatting/shadows.example.mjs b/examples/src/examples/gaussian-splatting/shadows.example.mjs new file mode 100644 index 00000000000..0f6bd4c7909 --- /dev/null +++ b/examples/src/examples/gaussian-splatting/shadows.example.mjs @@ -0,0 +1,169 @@ +// @config HIDDEN +import { deviceType, rootPath, fileImport } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const { ShadowCatcher } = await fileImport(`${rootPath}/static/scripts/esm/shadow-catcher.mjs`); + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +const gfxOptions = { + deviceTypes: [deviceType], + + // disable antialiasing as gaussian splats do not benefit from it and it's expensive + antialias: false +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.mouse = new pc.Mouse(document.body); +createOptions.touch = new pc.TouchDevice(document.body); + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem, + pc.GSplatComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler, pc.GSplatHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +const assets = { + biker: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/biker.compressed.ply` }), + hdri: new pc.Asset( + 'hdri', + 'texture', + { url: `${rootPath}/static/assets/hdri/st-peters-square.hdr` }, + { mipmaps: false } + ), + orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }) +}; + +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + // Setup projected skydome from HDR + const hdriTexture = assets.hdri.resource; + + // Generate high resolution cubemap for skybox + const skybox = pc.EnvLighting.generateSkyboxCubemap(hdriTexture); + app.scene.skybox = skybox; + + // Generate env-atlas for lighting + const lighting = pc.EnvLighting.generateLightingSource(hdriTexture); + const envAtlas = pc.EnvLighting.generateAtlas(lighting); + lighting.destroy(); + app.scene.envAtlas = envAtlas; + + // Set exposure and projected dome + app.scene.exposure = 0.4; + app.scene.sky.type = pc.SKYTYPE_DOME; + app.scene.sky.node.setLocalScale(new pc.Vec3(50, 50, 50)); + app.scene.sky.node.setLocalPosition(pc.Vec3.ZERO); + app.scene.sky.center = new pc.Vec3(0, 0.05, 0); + + // Customize GSplat material using unified mode material customization + // This sets the alpha clip value for all GSplat instances in unified mode + app.scene.gsplat.material.setParameter('alphaClip', 0.4); + app.scene.gsplat.material.update(); + + // Create first splat entity + const biker = new pc.Entity('biker'); + biker.addComponent('gsplat', { + asset: assets.biker, + castShadows: true, + unified: true + }); + biker.setLocalPosition(-1.5, 0.05, 0); + biker.setLocalEulerAngles(180, 90, 0); + biker.setLocalScale(0.7, 0.7, 0.7); + app.root.addChild(biker); + + // Create second splat entity + const biker2 = new pc.Entity('biker2'); + biker2.addComponent('gsplat', { + asset: assets.biker, + castShadows: true, + unified: true + }); + biker2.setLocalPosition(0.5, 0.05, 0); + biker2.setLocalEulerAngles(180, 0, 0); + biker2.setLocalScale(0.7, 0.7, 0.7); + app.root.addChild(biker2); + + // Create camera + const camera = new pc.Entity('camera'); + camera.addComponent('camera', { + clearColor: new pc.Color(1, 1, 1), + toneMapping: pc.TONEMAP_ACES, + fov: 60 + }); + camera.setLocalPosition(-3, 2, 4); + + // Add orbit camera script with mouse and touch support + camera.addComponent('script'); + camera.script?.create('orbitCamera', { + attributes: { + inertiaFactor: 0.2, + focusEntity: biker, + distanceMax: 10, + frameOnStart: false + } + }); + camera.script?.create('orbitCameraInputMouse'); + camera.script?.create('orbitCameraInputTouch'); + app.root.addChild(camera); + + // Create shadow catcher + const shadowCatcher = new pc.Entity('ShadowCatcher'); + shadowCatcher.addComponent('script'); + const shadowCatcherScript = shadowCatcher.script?.create(ShadowCatcher); + if (shadowCatcherScript) { + shadowCatcherScript.scale = new pc.Vec3(10, 10, 10); + } + app.root.addChild(shadowCatcher); + + // Shadow casting directional light casting shadows + const directionalLight = new pc.Entity('light'); + directionalLight.addComponent('light', { + type: 'directional', + color: pc.Color.WHITE, + castShadows: true, + intensity: 1, + shadowBias: 0.1, + normalOffsetBias: 0.05, + shadowDistance: 20, + shadowIntensity: 0.5, + shadowResolution: 2048, + shadowType: pc.SHADOW_PCF5_16F + }); + directionalLight.setEulerAngles(55, 30, 0); + app.root.addChild(directionalLight); + + // Auto-rotate light + let lightAngle = 0; + app.on('update', (/** @type {number} */ dt) => { + lightAngle += dt * 20; + directionalLight.setEulerAngles(55, 90 + lightAngle, 0); + }); +}); + +export { app }; diff --git a/examples/src/examples/gaussian-splatting/simple.example.mjs b/examples/src/examples/gaussian-splatting/simple.example.mjs index c2fe3543a02..afb02e9d3cf 100644 --- a/examples/src/examples/gaussian-splatting/simple.example.mjs +++ b/examples/src/examples/gaussian-splatting/simple.example.mjs @@ -6,8 +6,6 @@ window.focus(); const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable antialiasing as gaussian splats do not benefit from it and it's expensive antialias: false diff --git a/examples/src/examples/gaussian-splatting/spherical-harmonics.example.mjs b/examples/src/examples/gaussian-splatting/spherical-harmonics.example.mjs index ec8f4e38352..100b5d10b88 100644 --- a/examples/src/examples/gaussian-splatting/spherical-harmonics.example.mjs +++ b/examples/src/examples/gaussian-splatting/spherical-harmonics.example.mjs @@ -7,8 +7,6 @@ window.focus(); const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable antialiasing as gaussian splats do not benefit from it and it's expensive antialias: false diff --git a/examples/src/examples/gaussian-splatting/viewer.controls.mjs b/examples/src/examples/gaussian-splatting/viewer.controls.mjs new file mode 100644 index 00000000000..35536cd30fc --- /dev/null +++ b/examples/src/examples/gaussian-splatting/viewer.controls.mjs @@ -0,0 +1,102 @@ +import * as pc from 'playcanvas'; + +/** + * @param {import('../../app/components/Example.mjs').ControlOptions} options - The options. + * @returns {JSX.Element} The returned JSX Element. + */ +export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { + const { BindingTwoWay, BooleanInput, LabelGroup, Panel, SelectInput, SliderInput } = ReactPCUI; + return fragment( + jsx( + Panel, + { headerText: 'Scene' }, + jsx( + LabelGroup, + { text: 'Skydome' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'data.skydome' } + }) + ), + jsx( + LabelGroup, + { text: 'Orientation' }, + jsx(SelectInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'data.orientation' }, + type: 'number', + options: [ + { v: 0, t: '0°' }, + { v: 90, t: '90°' }, + { v: 180, t: '180°' }, + { v: 270, t: '270°' } + ] + }) + ) + ), + jsx( + Panel, + { headerText: 'Tone & Color' }, + jsx( + LabelGroup, + { text: 'Tonemapping' }, + jsx(SelectInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'data.tonemapping' }, + type: 'number', + options: [ + { v: pc.TONEMAP_LINEAR, t: 'LINEAR' }, + { v: pc.TONEMAP_FILMIC, t: 'FILMIC' }, + { v: pc.TONEMAP_HEJL, t: 'HEJL' }, + { v: pc.TONEMAP_ACES, t: 'ACES' }, + { v: pc.TONEMAP_ACES2, t: 'ACES2' }, + { v: pc.TONEMAP_NEUTRAL, t: 'NEUTRAL' } + ] + }) + ), + jsx( + LabelGroup, + { text: 'Exposure (EV)' }, + jsx(SliderInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'data.grading.exposure' }, + min: -10, + max: 10, + precision: 2 + }) + ), + jsx( + LabelGroup, + { text: 'Contrast' }, + jsx(SliderInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'data.grading.contrast' }, + min: 0.5, + max: 1.5, + precision: 2 + }) + ), + jsx( + LabelGroup, + { text: 'Bloom' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'data.bloom.enabled' } + }) + ), + jsx( + LabelGroup, + { text: 'Bloom Intensity' }, + jsx(SliderInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'data.bloom.intensity' }, + min: 0, + max: 0.2, + precision: 3 + }) + ) + ) + ); +}; diff --git a/examples/src/examples/gaussian-splatting/viewer.example.mjs b/examples/src/examples/gaussian-splatting/viewer.example.mjs new file mode 100644 index 00000000000..cb03ed2f707 --- /dev/null +++ b/examples/src/examples/gaussian-splatting/viewer.example.mjs @@ -0,0 +1,276 @@ +import { data } from 'examples/observer'; +import { deviceType, rootPath } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +// Create HTML overlay for drop instructions +const dropOverlay = document.createElement('div'); +dropOverlay.id = 'drop-overlay'; +dropOverlay.style.cssText = ` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 1000; +`; + +const dropBox = document.createElement('div'); +dropBox.style.cssText = ` + background: rgba(0, 0, 0, 0.45); + border: 2px dashed rgba(255, 255, 255, 0.6); + border-radius: 16px; + padding: 32px 48px; + font-family: Arial, sans-serif; + font-size: 24px; + color: white; +`; +dropBox.textContent = 'Drop .ply or .sog file to view'; +dropOverlay.appendChild(dropBox); +document.body.appendChild(dropOverlay); + +const gfxOptions = { + deviceTypes: [deviceType], + // Disable antialiasing as CameraFrame handles it + antialias: false +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.mouse = new pc.Mouse(document.body); +createOptions.touch = new pc.TouchDevice(document.body); + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem, + pc.GSplatComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler, pc.GSplatHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +// Load orbit camera script and HDRI +const assets = { + orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }), + hdri: new pc.Asset( + 'hdri', + 'texture', + { url: `${rootPath}/static/assets/hdri/wide-street.hdr` }, + { mipmaps: false } + ) +}; + +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + let splatEntity = null; + + // Create camera at startup so skydome is visible before dropping files + const camera = new pc.Entity('camera'); + camera.addComponent('camera', { + clearColor: new pc.Color(0, 0, 0), + fov: 60, + farClip: 1000 + }); + camera.setLocalPosition(0, 2, 5); + app.root.addChild(camera); + + // Setup CameraFrame + const cameraFrame = new pc.CameraFrame(app, camera.camera); + cameraFrame.rendering.renderFormats = [ + pc.PIXELFORMAT_RGBA16F, + pc.PIXELFORMAT_RGBA32F, + pc.PIXELFORMAT_111110F + ]; + cameraFrame.rendering.samples = 1; + cameraFrame.grading.enabled = true; + + // Setup skydome toggle function + const applySkydome = () => { + const enabled = data.get('data.skydome'); + if (enabled) { + const hdriTexture = assets.hdri.resource; + + // Generate high resolution cubemap for skybox + const skybox = pc.EnvLighting.generateSkyboxCubemap(hdriTexture); + app.scene.skybox = skybox; + + // Generate env-atlas for lighting + const lighting = pc.EnvLighting.generateLightingSource(hdriTexture); + const envAtlas = pc.EnvLighting.generateAtlas(lighting); + lighting.destroy(); + app.scene.envAtlas = envAtlas; + } else { + app.scene.skybox = null; + app.scene.envAtlas = null; + } + }; + + // Initialize data values + data.set('data', { + skydome: false, + orientation: 180, + tonemapping: pc.TONEMAP_LINEAR, + grading: { + exposure: 0, // 0 EV = no change + contrast: 1 + }, + bloom: { + enabled: false, + intensity: 0.03 + } + }); + + // Apply initial skydome setting + applySkydome(); + + // Apply settings function + const applySettings = () => { + cameraFrame.rendering.toneMapping = data.get('data.tonemapping'); + + // Convert exposure EV (F-stops) to brightness multiplier + // Each stop doubles or halves brightness: multiplier = 2^(EV) + const exposureEV = data.get('data.grading.exposure'); + cameraFrame.grading.brightness = Math.pow(2, exposureEV); + + cameraFrame.grading.contrast = data.get('data.grading.contrast'); + + // Bloom - only enabled if toggle is on + const bloomEnabled = data.get('data.bloom.enabled'); + const bloomIntensity = data.get('data.bloom.intensity'); + cameraFrame.bloom.intensity = bloomEnabled ? bloomIntensity : 0; + if (bloomEnabled) { + cameraFrame.bloom.blurLevel = 7; + } + + cameraFrame.update(); + }; + + // Apply initial settings + applySettings(); + + // Listen for changes + data.on('*:set', (/** @type {string} */ path) => { + if (path === 'data.skydome') { + applySkydome(); + } else if (path === 'data.orientation') { + // Apply orientation to splat entity + if (splatEntity) { + const orientation = data.get('data.orientation'); + splatEntity.setLocalEulerAngles(orientation, 0, 0); + } + } else { + applySettings(); + } + }); + + // Setup drag and drop handlers + canvas.addEventListener('dragover', (e) => { + e.preventDefault(); + }); + + canvas.addEventListener('drop', async (e) => { + e.preventDefault(); + + const file = e.dataTransfer.files[0]; + if (!file) return; + + const fileName = file.name.toLowerCase(); + if (!fileName.endsWith('.ply') && !fileName.endsWith('.sog')) { + console.warn('Please drop a .ply or .sog file'); + return; + } + + // Hide instructions overlay + dropOverlay.style.display = 'none'; + + // Create blob URL and load asset using loadFromUrlAndFilename + // This method is specifically for blob assets where the URL doesn't identify the format + const blobUrl = URL.createObjectURL(file); + + const asset = await new Promise((resolve, reject) => { + app.assets.loadFromUrlAndFilename(blobUrl, file.name, 'gsplat', (err, loadedAsset) => { + if (err) { + reject(err); + } else { + resolve(loadedAsset); + } + }); + }); + + // Create gsplat entity + const entity = new pc.Entity(file.name); + entity.addComponent('gsplat', { + asset: asset, + unified: true + }); + entity.setLocalEulerAngles(180, 0, 0); + app.root.addChild(entity); + + // Store reference for orientation updates + splatEntity = entity; + + // Wait a frame for customAabb to be available + await new Promise((resolve) => { + requestAnimationFrame(resolve); + }); + + // Get bounds for framing + const aabb = entity.gsplat.customAabb; + if (!aabb) { + console.warn('customAabb not available'); + return; + } + + const center = aabb.center; + const size = aabb.halfExtents.length() * 2; + const cameraDistance = size * 2.5; + + // Update camera for the loaded splat + camera.camera.farClip = size * 10; + camera.setLocalPosition( + center.x, + center.y + size * 0.3, + center.z + cameraDistance + ); + + // Add orbit camera script + camera.addComponent('script'); + camera.script.create('orbitCamera', { + attributes: { + inertiaFactor: 0.2, + focusEntity: entity, + distanceMax: size * 5, + frameOnStart: true + } + }); + camera.script.create('orbitCameraInputMouse'); + camera.script.create('orbitCameraInputTouch'); + }); +}); + +export { app }; diff --git a/examples/src/examples/gizmos/transform-rotate.example.mjs b/examples/src/examples/gizmos/transform-rotate.example.mjs index aae1d59bc60..9083b98bc5d 100644 --- a/examples/src/examples/gizmos/transform-rotate.example.mjs +++ b/examples/src/examples/gizmos/transform-rotate.example.mjs @@ -9,9 +9,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/gizmos/transform-scale.example.mjs b/examples/src/examples/gizmos/transform-scale.example.mjs index 048545d1dc6..a8324436620 100644 --- a/examples/src/examples/gizmos/transform-scale.example.mjs +++ b/examples/src/examples/gizmos/transform-scale.example.mjs @@ -9,9 +9,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/gizmos/transform-translate.example.mjs b/examples/src/examples/gizmos/transform-translate.example.mjs index e72de8d50d4..7a5129c7320 100644 --- a/examples/src/examples/gizmos/transform-translate.example.mjs +++ b/examples/src/examples/gizmos/transform-translate.example.mjs @@ -9,9 +9,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/ambient-occlusion.example.mjs b/examples/src/examples/graphics/ambient-occlusion.example.mjs index 9601daa6502..1c92a9bcb9c 100644 --- a/examples/src/examples/graphics/ambient-occlusion.example.mjs +++ b/examples/src/examples/graphics/ambient-occlusion.example.mjs @@ -25,9 +25,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/area-lights.example.mjs b/examples/src/examples/graphics/area-lights.example.mjs index da514ac46d0..2c9efa6ffc6 100644 --- a/examples/src/examples/graphics/area-lights.example.mjs +++ b/examples/src/examples/graphics/area-lights.example.mjs @@ -19,9 +19,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/area-picker.example.mjs b/examples/src/examples/graphics/area-picker.example.mjs index 68112276cdb..139a1e1c673 100644 --- a/examples/src/examples/graphics/area-picker.example.mjs +++ b/examples/src/examples/graphics/area-picker.example.mjs @@ -1,3 +1,4 @@ +// @config DESCRIPTION Click on objects to detect world space intersection. Objects within the colored rectangles are highlighted. import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -5,7 +6,6 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const assets = { - bloom: new pc.Asset('bloom', 'script', { url: `${rootPath}/static/scripts/posteffects/posteffect-bloom.js` }), helipad: new pc.Asset( 'helipad-env-atlas', 'texture', @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -28,8 +26,8 @@ createOptions.graphicsDevice = device; createOptions.mouse = new pc.Mouse(document.body); createOptions.touch = new pc.TouchDevice(document.body); -createOptions.componentSystems = [pc.RenderComponentSystem, pc.CameraComponentSystem, pc.ScriptComponentSystem]; -createOptions.resourceHandlers = [pc.ScriptHandler, pc.TextureHandler]; +createOptions.componentSystems = [pc.RenderComponentSystem, pc.CameraComponentSystem]; +createOptions.resourceHandlers = [pc.TextureHandler]; const app = new pc.AppBase(canvas); app.init(createOptions); @@ -86,7 +84,7 @@ assetListLoader.load(() => { // Create an instance of the picker class // Lets use quarter of the resolution to improve performance - this will miss very small objects, but it's ok in our case - const picker = new pc.Picker(app, canvas.clientWidth * pickerScale, canvas.clientHeight * pickerScale); + const picker = new pc.Picker(app, canvas.clientWidth * pickerScale, canvas.clientHeight * pickerScale, true); /** * Helper function to create a primitive with shape type, position, scale. @@ -124,18 +122,14 @@ assetListLoader.load(() => { camera.addComponent('camera', { clearColor: new pc.Color(0.1, 0.1, 0.1) }); - - // add bloom postprocessing (this is ignored by the picker) - camera.addComponent('script'); - camera.script.create('bloom', { - attributes: { - bloomIntensity: 1, - bloomThreshold: 0.7, - blurAmount: 4 - } - }); app.root.addChild(camera); + // ------ Custom render passes with bloom ------ + const cameraFrame = new pc.CameraFrame(app, camera.camera); + cameraFrame.bloom.intensity = 0.01; + cameraFrame.bloom.blurLevel = 4; + cameraFrame.update(); + /** * Function to draw a 2D rectangle in the screen space coordinates. * @@ -167,6 +161,7 @@ assetListLoader.load(() => { */ function highlightMaterial(material, color) { material.emissive = color; + material.emissiveIntensity = 30; material.update(); } @@ -178,6 +173,30 @@ assetListLoader.load(() => { const worldLayer = app.scene.layers.getLayerByName('World'); const pickerLayers = [worldLayer]; + // marker sphere to show the picked world point + const marker = createPrimitive('sphere', pc.Vec3.ZERO, new pc.Vec3(0.2, 0.2, 0.2)); + const markerMaterial = new pc.StandardMaterial(); + markerMaterial.emissive = new pc.Color(0, 1, 0); + markerMaterial.emissiveIntensity = 100; + marker.render.material = markerMaterial; + marker.render.meshInstances[0].pick = false; + marker.enabled = false; + app.root.addChild(marker); + + // store pending pick request + /** @type {{ x: number, y: number } | null} */ + let pendingPickRequest = null; + + // handle mouse click to pick world point + const mouse = new pc.Mouse(document.body); + mouse.on('mousedown', (event) => { + // store the pick request to be processed after picker.prepare + pendingPickRequest = { + x: event.x * pickerScale, + y: event.y * pickerScale + }; + }); + // update each frame let time = 0; app.on('update', (/** @type {number} */ dt) => { @@ -251,6 +270,8 @@ assetListLoader.load(() => { // turn off previously highlighted meshes for (let h = 0; h < highlights.length; h++) { highlightMaterial(highlights[h], pc.Color.BLACK); + // Reset emissive intensity when turning off + highlights[h].emissiveIntensity = 0; } highlights.length = 0; @@ -268,6 +289,33 @@ assetListLoader.load(() => { } } }); + + // process pending pick request after picker.prepare has been called + if (pendingPickRequest && picker) { + const { x, y } = pendingPickRequest; + pendingPickRequest = null; + + picker.getWorldPointAsync(x, y).then((worldPoint) => { + if (worldPoint) { + marker.enabled = true; + marker.setPosition(worldPoint); + } else { + marker.enabled = false; + } + }); + } + + // display the picker's buffers side by side in the bottom right corner + // color buffer (left) and depth buffer (right), with equal margins from edges + if (picker.colorBuffer) { + // @ts-ignore engine-tsd + app.drawTexture(0.55, -0.77, 0.2, 0.2, picker.colorBuffer); + } + + if (picker.depthBuffer) { + // @ts-ignore engine-tsd + app.drawTexture(0.77, -0.77, 0.2, 0.2, picker.depthBuffer); + } }); }); diff --git a/examples/src/examples/graphics/asset-viewer.example.mjs b/examples/src/examples/graphics/asset-viewer.example.mjs index 02cb0c7dd6c..fe29e437dba 100644 --- a/examples/src/examples/graphics/asset-viewer.example.mjs +++ b/examples/src/examples/graphics/asset-viewer.example.mjs @@ -22,9 +22,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/batching-dynamic.example.mjs b/examples/src/examples/graphics/batching-dynamic.example.mjs index 4579f68c781..004c1c69af6 100644 --- a/examples/src/examples/graphics/batching-dynamic.example.mjs +++ b/examples/src/examples/graphics/batching-dynamic.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/clustered-area-lights.example.mjs b/examples/src/examples/graphics/clustered-area-lights.example.mjs index 42af210d94a..b36cee21bae 100644 --- a/examples/src/examples/graphics/clustered-area-lights.example.mjs +++ b/examples/src/examples/graphics/clustered-area-lights.example.mjs @@ -22,8 +22,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // enable HDR rendering if supported displayFormat: pc.DISPLAYFORMAT_HDR diff --git a/examples/src/examples/graphics/clustered-lighting.example.mjs b/examples/src/examples/graphics/clustered-lighting.example.mjs index 2c03b6730d3..8c4b6c18ff8 100644 --- a/examples/src/examples/graphics/clustered-lighting.example.mjs +++ b/examples/src/examples/graphics/clustered-lighting.example.mjs @@ -12,8 +12,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // enable HDR rendering if supported displayFormat: pc.DISPLAYFORMAT_HDR diff --git a/examples/src/examples/graphics/clustered-omni-shadows.example.mjs b/examples/src/examples/graphics/clustered-omni-shadows.example.mjs index 00be930ef6c..cddc856b293 100644 --- a/examples/src/examples/graphics/clustered-omni-shadows.example.mjs +++ b/examples/src/examples/graphics/clustered-omni-shadows.example.mjs @@ -29,9 +29,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/clustered-spot-shadows.example.mjs b/examples/src/examples/graphics/clustered-spot-shadows.example.mjs index 23077776056..a22e7af36c4 100644 --- a/examples/src/examples/graphics/clustered-spot-shadows.example.mjs +++ b/examples/src/examples/graphics/clustered-spot-shadows.example.mjs @@ -20,9 +20,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/custom-compose-shader.example.mjs b/examples/src/examples/graphics/custom-compose-shader.example.mjs index eaf348624aa..d52241acc96 100644 --- a/examples/src/examples/graphics/custom-compose-shader.example.mjs +++ b/examples/src/examples/graphics/custom-compose-shader.example.mjs @@ -20,8 +20,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas // to avoid the additional cost. This is only used for the UI which renders on top of the diff --git a/examples/src/examples/graphics/depth-of-field.example.mjs b/examples/src/examples/graphics/depth-of-field.example.mjs index d342ecec6d2..fcfa7bcd12c 100644 --- a/examples/src/examples/graphics/depth-of-field.example.mjs +++ b/examples/src/examples/graphics/depth-of-field.example.mjs @@ -20,8 +20,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas // to avoid the additional cost. This is only used for the UI which renders on top of the diff --git a/examples/src/examples/graphics/dithered-transparency.example.mjs b/examples/src/examples/graphics/dithered-transparency.example.mjs index 9d55440e9f2..04f0592ea59 100644 --- a/examples/src/examples/graphics/dithered-transparency.example.mjs +++ b/examples/src/examples/graphics/dithered-transparency.example.mjs @@ -19,8 +19,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable anti-aliasing as TAA is used to smooth edges antialias: false diff --git a/examples/src/examples/graphics/hdr.example.mjs b/examples/src/examples/graphics/hdr.example.mjs index 46d7371a599..8598ff81e25 100644 --- a/examples/src/examples/graphics/hdr.example.mjs +++ b/examples/src/examples/graphics/hdr.example.mjs @@ -21,8 +21,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas // to avoid the additional cost. This is only used for the UI which renders on top of the diff --git a/examples/src/examples/graphics/hierarchy.example.mjs b/examples/src/examples/graphics/hierarchy.example.mjs index d31efc31245..1cc3e2e4ab2 100644 --- a/examples/src/examples/graphics/hierarchy.example.mjs +++ b/examples/src/examples/graphics/hierarchy.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/instancing-basic.example.mjs b/examples/src/examples/graphics/instancing-basic.example.mjs index d2d33f5cd1b..bc6873eede6 100644 --- a/examples/src/examples/graphics/instancing-basic.example.mjs +++ b/examples/src/examples/graphics/instancing-basic.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/instancing-custom.example.mjs b/examples/src/examples/graphics/instancing-custom.example.mjs index f25a90f0baf..7ba5d16ccae 100644 --- a/examples/src/examples/graphics/instancing-custom.example.mjs +++ b/examples/src/examples/graphics/instancing-custom.example.mjs @@ -1,4 +1,5 @@ // @config DESCRIPTION This example demonstrates how to customize the shader handling the instancing of a StandardMaterial. +import files from 'examples/files'; import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -15,9 +16,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -98,34 +97,8 @@ assetListLoader.load(() => { // and a custom instancing shader chunk, which will be used in case the mesh instance has instancing enabled material.shaderChunksVersion = '2.8'; - material.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set('transformInstancingVS', ` - - // instancing attributes - attribute vec3 aInstPosition; - attribute float aInstScale; - - // uniforms - uniform float uTime; - uniform vec3 uCenter; - - // all instancing chunk needs to do is to implement getModelMatrix function, which returns a world matrix for the instance - mat4 getModelMatrix() { - - // we have world position in aInstPosition, but modify it based on distance from uCenter for some displacement effect - vec3 direction = aInstPosition - uCenter; - float distanceFromCenter = length(direction); - float displacementIntensity = exp(-distanceFromCenter * 0.2) ; //* (1.9 + abs(sin(uTime * 1.5))); - vec3 worldPos = aInstPosition - direction * displacementIntensity; - - // create matrix based on the modified poition, and scale - return mat4( - vec4(aInstScale, 0.0, 0.0, 0.0), - vec4(0.0, aInstScale, 0.0, 0.0), - vec4(0.0, 0.0, aInstScale, 0.0), - vec4(worldPos, 1.0) - ); - } - `); + material.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set('transformInstancingVS', files['transform-instancing.glsl.vert']); + material.getShaderChunks(pc.SHADERLANGUAGE_WGSL).set('transformInstancingVS', files['transform-instancing.wgsl.vert']); material.update(); diff --git a/examples/src/examples/graphics/instancing-custom.transform-instancing.glsl.vert b/examples/src/examples/graphics/instancing-custom.transform-instancing.glsl.vert new file mode 100644 index 00000000000..679e3919563 --- /dev/null +++ b/examples/src/examples/graphics/instancing-custom.transform-instancing.glsl.vert @@ -0,0 +1,27 @@ + +// instancing attributes +attribute vec3 aInstPosition; +attribute float aInstScale; + +// uniforms +uniform float uTime; +uniform vec3 uCenter; + +// all instancing chunk needs to do is to implement getModelMatrix function, which returns a world matrix for the instance +mat4 getModelMatrix() { + + // we have world position in aInstPosition, but modify it based on distance from uCenter for some displacement effect + vec3 direction = aInstPosition - uCenter; + float distanceFromCenter = length(direction); + float displacementIntensity = exp(-distanceFromCenter * 0.2) ; //* (1.9 + abs(sin(uTime * 1.5))); + vec3 worldPos = aInstPosition - direction * displacementIntensity; + + // create matrix based on the modified poition, and scale + return mat4( + vec4(aInstScale, 0.0, 0.0, 0.0), + vec4(0.0, aInstScale, 0.0, 0.0), + vec4(0.0, 0.0, aInstScale, 0.0), + vec4(worldPos, 1.0) + ); +} + diff --git a/examples/src/examples/graphics/instancing-custom.transform-instancing.wgsl.vert b/examples/src/examples/graphics/instancing-custom.transform-instancing.wgsl.vert new file mode 100644 index 00000000000..185c3dbf37b --- /dev/null +++ b/examples/src/examples/graphics/instancing-custom.transform-instancing.wgsl.vert @@ -0,0 +1,27 @@ + +// instancing attributes +attribute aInstPosition: vec3f; +attribute aInstScale: f32; + +// uniforms +uniform uTime: f32; +uniform uCenter: vec3f; + +// all instancing chunk needs to do is to implement getModelMatrix function, which returns a world matrix for the instance +fn getModelMatrix() -> mat4x4f { + + // we have world position in aInstPosition, but modify it based on distance from uCenter for some displacement effect + var direction: vec3f = aInstPosition - uniform.uCenter; + var distanceFromCenter: f32 = length(direction); + var displacementIntensity: f32 = exp(-distanceFromCenter * 0.2); //* (1.9 + abs(sin(uniform.uTime * 1.5))); + var worldPos: vec3f = aInstPosition - direction * displacementIntensity; + + // create matrix based on the modified poition, and scale + return mat4x4f( + vec4f(aInstScale, 0.0, 0.0, 0.0), + vec4f(0.0, aInstScale, 0.0, 0.0), + vec4f(0.0, 0.0, aInstScale, 0.0), + vec4f(worldPos, 1.0) + ); +} + diff --git a/examples/src/examples/graphics/instancing-glb.example.mjs b/examples/src/examples/graphics/instancing-glb.example.mjs index 00226ca4eaa..c3256692ea5 100644 --- a/examples/src/examples/graphics/instancing-glb.example.mjs +++ b/examples/src/examples/graphics/instancing-glb.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/instancing-gooch.example.mjs b/examples/src/examples/graphics/instancing-gooch.example.mjs index 8b7d0cc7d27..fe70f9d0b3a 100644 --- a/examples/src/examples/graphics/instancing-gooch.example.mjs +++ b/examples/src/examples/graphics/instancing-gooch.example.mjs @@ -23,9 +23,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/layers.example.mjs b/examples/src/examples/graphics/layers.example.mjs index dd01f6ee4ed..fcc0d1bea62 100644 --- a/examples/src/examples/graphics/layers.example.mjs +++ b/examples/src/examples/graphics/layers.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/light-physical-units.example.mjs b/examples/src/examples/graphics/light-physical-units.example.mjs index bf3dc549ca9..ab0b4623643 100644 --- a/examples/src/examples/graphics/light-physical-units.example.mjs +++ b/examples/src/examples/graphics/light-physical-units.example.mjs @@ -22,9 +22,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/lights-baked-a-o.example.mjs b/examples/src/examples/graphics/lights-baked-a-o.example.mjs index 69e4bc884a9..58b38940a97 100644 --- a/examples/src/examples/graphics/lights-baked-a-o.example.mjs +++ b/examples/src/examples/graphics/lights-baked-a-o.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/lights-baked.example.mjs b/examples/src/examples/graphics/lights-baked.example.mjs index 388e2041ae9..7b4cc6f166c 100644 --- a/examples/src/examples/graphics/lights-baked.example.mjs +++ b/examples/src/examples/graphics/lights-baked.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/lights.example.mjs b/examples/src/examples/graphics/lights.example.mjs index 60628bfd353..647381b68a2 100644 --- a/examples/src/examples/graphics/lights.example.mjs +++ b/examples/src/examples/graphics/lights.example.mjs @@ -39,9 +39,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/lines.example.mjs b/examples/src/examples/graphics/lines.example.mjs index ed9c605e94c..75743377b4e 100644 --- a/examples/src/examples/graphics/lines.example.mjs +++ b/examples/src/examples/graphics/lines.example.mjs @@ -14,9 +14,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/mesh-decals.example.mjs b/examples/src/examples/graphics/mesh-decals.example.mjs index e4b677068ce..11dc596cec0 100644 --- a/examples/src/examples/graphics/mesh-decals.example.mjs +++ b/examples/src/examples/graphics/mesh-decals.example.mjs @@ -10,8 +10,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // enable HDR rendering if supported displayFormat: pc.DISPLAYFORMAT_HDR diff --git a/examples/src/examples/graphics/mesh-deformation.example.mjs b/examples/src/examples/graphics/mesh-deformation.example.mjs index fe1f80fe796..8d34b2ed062 100644 --- a/examples/src/examples/graphics/mesh-deformation.example.mjs +++ b/examples/src/examples/graphics/mesh-deformation.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/mesh-generation.example.mjs b/examples/src/examples/graphics/mesh-generation.example.mjs index 2e4fda9d1a9..e45e2bf039f 100644 --- a/examples/src/examples/graphics/mesh-generation.example.mjs +++ b/examples/src/examples/graphics/mesh-generation.example.mjs @@ -11,9 +11,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/mesh-morph-many.example.mjs b/examples/src/examples/graphics/mesh-morph-many.example.mjs index 54303cd9ef4..0403a80de32 100644 --- a/examples/src/examples/graphics/mesh-morph-many.example.mjs +++ b/examples/src/examples/graphics/mesh-morph-many.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/mesh-morph.example.mjs b/examples/src/examples/graphics/mesh-morph.example.mjs index 87f21a20bc6..a8dbcdb6ba3 100644 --- a/examples/src/examples/graphics/mesh-morph.example.mjs +++ b/examples/src/examples/graphics/mesh-morph.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/model-asset.example.mjs b/examples/src/examples/graphics/model-asset.example.mjs index e798d2d7248..53141ad902f 100644 --- a/examples/src/examples/graphics/model-asset.example.mjs +++ b/examples/src/examples/graphics/model-asset.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/model-textured-box.example.mjs b/examples/src/examples/graphics/model-textured-box.example.mjs index acfbab6d675..d90cfaae48d 100644 --- a/examples/src/examples/graphics/model-textured-box.example.mjs +++ b/examples/src/examples/graphics/model-textured-box.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/multi-draw-instanced-multi-platform.example.mjs b/examples/src/examples/graphics/multi-draw-instanced-multi-platform.example.mjs index e9b9a4b0faf..a475a7044ee 100644 --- a/examples/src/examples/graphics/multi-draw-instanced-multi-platform.example.mjs +++ b/examples/src/examples/graphics/multi-draw-instanced-multi-platform.example.mjs @@ -16,9 +16,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/multi-draw-instanced.example.mjs b/examples/src/examples/graphics/multi-draw-instanced.example.mjs index 1cdc67aced9..fd27c34dffa 100644 --- a/examples/src/examples/graphics/multi-draw-instanced.example.mjs +++ b/examples/src/examples/graphics/multi-draw-instanced.example.mjs @@ -16,9 +16,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/multi-draw.example.mjs b/examples/src/examples/graphics/multi-draw.example.mjs index 08d6fd34cbb..02a23b81b22 100644 --- a/examples/src/examples/graphics/multi-draw.example.mjs +++ b/examples/src/examples/graphics/multi-draw.example.mjs @@ -25,9 +25,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const { CameraControls } = await fileImport(`${rootPath}/static/scripts/esm/camera-controls.mjs`); diff --git a/examples/src/examples/graphics/multi-render-targets.example.mjs b/examples/src/examples/graphics/multi-render-targets.example.mjs index 6d500795e97..33ec7adace8 100644 --- a/examples/src/examples/graphics/multi-render-targets.example.mjs +++ b/examples/src/examples/graphics/multi-render-targets.example.mjs @@ -23,9 +23,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/multi-view.example.mjs b/examples/src/examples/graphics/multi-view.example.mjs index f0eba0ca40c..30a26c23b2e 100644 --- a/examples/src/examples/graphics/multi-view.example.mjs +++ b/examples/src/examples/graphics/multi-view.example.mjs @@ -28,9 +28,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/outlines-colored.example.mjs b/examples/src/examples/graphics/outlines-colored.example.mjs index 6bd3571e459..c9b8266453a 100644 --- a/examples/src/examples/graphics/outlines-colored.example.mjs +++ b/examples/src/examples/graphics/outlines-colored.example.mjs @@ -23,9 +23,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/painter.example.mjs b/examples/src/examples/graphics/painter.example.mjs index 6dd7aac630b..ccc77d6b838 100644 --- a/examples/src/examples/graphics/painter.example.mjs +++ b/examples/src/examples/graphics/painter.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/particles-anim-index.example.mjs b/examples/src/examples/graphics/particles-anim-index.example.mjs index 77f5097edd9..bbbb321e04f 100644 --- a/examples/src/examples/graphics/particles-anim-index.example.mjs +++ b/examples/src/examples/graphics/particles-anim-index.example.mjs @@ -11,9 +11,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/particles-mesh.example.mjs b/examples/src/examples/graphics/particles-mesh.example.mjs index c645c0403df..45bba6a1ba7 100644 --- a/examples/src/examples/graphics/particles-mesh.example.mjs +++ b/examples/src/examples/graphics/particles-mesh.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/particles-random-sprites.example.mjs b/examples/src/examples/graphics/particles-random-sprites.example.mjs index fe8086384cd..b4fbeaefcf3 100644 --- a/examples/src/examples/graphics/particles-random-sprites.example.mjs +++ b/examples/src/examples/graphics/particles-random-sprites.example.mjs @@ -14,9 +14,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/particles-snow.example.mjs b/examples/src/examples/graphics/particles-snow.example.mjs index 73a4ca7a6c0..700b2275bf5 100644 --- a/examples/src/examples/graphics/particles-snow.example.mjs +++ b/examples/src/examples/graphics/particles-snow.example.mjs @@ -11,9 +11,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/particles-spark.example.mjs b/examples/src/examples/graphics/particles-spark.example.mjs index e533744c02c..3a05cb1f8e6 100644 --- a/examples/src/examples/graphics/particles-spark.example.mjs +++ b/examples/src/examples/graphics/particles-spark.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/portal.example.mjs b/examples/src/examples/graphics/portal.example.mjs index 95802f84850..a5e7e5c6d0c 100644 --- a/examples/src/examples/graphics/portal.example.mjs +++ b/examples/src/examples/graphics/portal.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -204,7 +202,7 @@ assetListLoader.load(() => { portalEntity.setLocalScale(0.02, 0.02, 0.02); group.addChild(portalEntity); - // Create a statue entity, whic is visible inside the portal only + // Create a statue entity, which is visible inside the portal only const statue = assets.statue.resource.instantiateRenderEntity(); statue.addComponent('script'); statue.script.create('portalGeometry', { @@ -216,7 +214,7 @@ assetListLoader.load(() => { statue.setLocalScale(0.25, 0.25, 0.25); group.addChild(statue); - // Create a bitmoji entity, whic is visible outside the portal only + // Create a bitmoji entity, which is visible outside the portal only const bitmoji = assets.bitmoji.resource.instantiateRenderEntity(); bitmoji.addComponent('script'); bitmoji.script.create('portalGeometry', { diff --git a/examples/src/examples/graphics/post-processing.example.mjs b/examples/src/examples/graphics/post-processing.example.mjs index 86d50566514..61092ef070f 100644 --- a/examples/src/examples/graphics/post-processing.example.mjs +++ b/examples/src/examples/graphics/post-processing.example.mjs @@ -27,8 +27,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas // to avoid the additional cost. This is only used for the UI which renders on top of the diff --git a/examples/src/examples/graphics/reflection-box.example.mjs b/examples/src/examples/graphics/reflection-box.example.mjs index c9fa239703b..f09cacb0b71 100644 --- a/examples/src/examples/graphics/reflection-box.example.mjs +++ b/examples/src/examples/graphics/reflection-box.example.mjs @@ -12,9 +12,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -289,7 +287,7 @@ assetListLoader.load(() => { farClip: 500 }); - // Add a cubemap renderer script, which renders to a cubemap of size 128 with mipmaps, which is directly useable + // Add a cubemap renderer script, which renders to a cubemap of size 128 with mipmaps, which is directly usable // as a lighting source for envAtlas generation // Position it in the center of the room. probe.script.create('cubemapRenderer', { diff --git a/examples/src/examples/graphics/reflection-cubemap.example.mjs b/examples/src/examples/graphics/reflection-cubemap.example.mjs index 7541c5d42b5..e98ac2935b9 100644 --- a/examples/src/examples/graphics/reflection-cubemap.example.mjs +++ b/examples/src/examples/graphics/reflection-cubemap.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/reflection-planar.example.mjs b/examples/src/examples/graphics/reflection-planar.example.mjs index 551470cf8e8..114d2ebcc30 100644 --- a/examples/src/examples/graphics/reflection-planar.example.mjs +++ b/examples/src/examples/graphics/reflection-planar.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/render-asset.example.mjs b/examples/src/examples/graphics/render-asset.example.mjs index 5898ff61a25..14e0b64f4a7 100644 --- a/examples/src/examples/graphics/render-asset.example.mjs +++ b/examples/src/examples/graphics/render-asset.example.mjs @@ -16,9 +16,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/render-pass.example.mjs b/examples/src/examples/graphics/render-pass.example.mjs index e7f0b734896..4669080bb7d 100644 --- a/examples/src/examples/graphics/render-pass.example.mjs +++ b/examples/src/examples/graphics/render-pass.example.mjs @@ -69,9 +69,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/render-to-texture.example.mjs b/examples/src/examples/graphics/render-to-texture.example.mjs index 803d2eb56d3..43a3d9bf584 100644 --- a/examples/src/examples/graphics/render-to-texture.example.mjs +++ b/examples/src/examples/graphics/render-to-texture.example.mjs @@ -25,9 +25,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/shadow-cascades.example.mjs b/examples/src/examples/graphics/shadow-cascades.example.mjs index 449cfca61b2..dba354ff006 100644 --- a/examples/src/examples/graphics/shadow-cascades.example.mjs +++ b/examples/src/examples/graphics/shadow-cascades.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/shadow-catcher.example.mjs b/examples/src/examples/graphics/shadow-catcher.example.mjs index cf97135d112..53fa1826893 100644 --- a/examples/src/examples/graphics/shadow-catcher.example.mjs +++ b/examples/src/examples/graphics/shadow-catcher.example.mjs @@ -19,8 +19,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // enable HDR rendering if supported displayFormat: pc.DISPLAYFORMAT_HDR diff --git a/examples/src/examples/graphics/shadow-soft.example.mjs b/examples/src/examples/graphics/shadow-soft.example.mjs index d2b7b2dab92..1206ddcc1be 100644 --- a/examples/src/examples/graphics/shadow-soft.example.mjs +++ b/examples/src/examples/graphics/shadow-soft.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -152,7 +150,7 @@ assetListLoader.load(() => { app.root.addChild(camera); // Create a directional light casting soft shadows - const dirLight = new pc.Entity('Cascaded Light'); + const dirLight = new pc.Entity('MainLight'); dirLight.addComponent('light', { ...{ type: 'directional', diff --git a/examples/src/examples/graphics/shapes.example.mjs b/examples/src/examples/graphics/shapes.example.mjs index f88674d97bf..fdcec6b8bba 100644 --- a/examples/src/examples/graphics/shapes.example.mjs +++ b/examples/src/examples/graphics/shapes.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/sky.example.mjs b/examples/src/examples/graphics/sky.example.mjs index b27fff0d17f..04abacceece 100644 --- a/examples/src/examples/graphics/sky.example.mjs +++ b/examples/src/examples/graphics/sky.example.mjs @@ -24,8 +24,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // enable HDR rendering if supported displayFormat: pc.DISPLAYFORMAT_HDR diff --git a/examples/src/examples/graphics/taa.example.mjs b/examples/src/examples/graphics/taa.example.mjs index a794b78f6b0..47dad62e8aa 100644 --- a/examples/src/examples/graphics/taa.example.mjs +++ b/examples/src/examples/graphics/taa.example.mjs @@ -19,8 +19,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable anti-aliasing as TAA is used to smooth edges antialias: false diff --git a/examples/src/examples/graphics/texture-basis.example.mjs b/examples/src/examples/graphics/texture-basis.example.mjs index 0cfb0b35524..1ed7bb74f41 100644 --- a/examples/src/examples/graphics/texture-basis.example.mjs +++ b/examples/src/examples/graphics/texture-basis.example.mjs @@ -29,9 +29,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/transform-feedback.example.mjs b/examples/src/examples/graphics/transform-feedback.example.mjs index 20c8d91ed2a..e13c8361049 100644 --- a/examples/src/examples/graphics/transform-feedback.example.mjs +++ b/examples/src/examples/graphics/transform-feedback.example.mjs @@ -7,9 +7,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/graphics/video-texture.example.mjs b/examples/src/examples/graphics/video-texture.example.mjs index 18960d2e99d..676b2853a6c 100644 --- a/examples/src/examples/graphics/video-texture.example.mjs +++ b/examples/src/examples/graphics/video-texture.example.mjs @@ -5,9 +5,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/input/gamepad.example.mjs b/examples/src/examples/input/gamepad.example.mjs index a78486fb4c6..f66d057d4b7 100644 --- a/examples/src/examples/input/gamepad.example.mjs +++ b/examples/src/examples/input/gamepad.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/input/keyboard.example.mjs b/examples/src/examples/input/keyboard.example.mjs index bcf10e4426f..e0a38c461f8 100644 --- a/examples/src/examples/input/keyboard.example.mjs +++ b/examples/src/examples/input/keyboard.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/input/mouse.example.mjs b/examples/src/examples/input/mouse.example.mjs index cfeaedd97a0..997c809e000 100644 --- a/examples/src/examples/input/mouse.example.mjs +++ b/examples/src/examples/input/mouse.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/loaders/bundle.example.mjs b/examples/src/examples/loaders/bundle.example.mjs index a4237a02f07..f3b9d476aad 100644 --- a/examples/src/examples/loaders/bundle.example.mjs +++ b/examples/src/examples/loaders/bundle.example.mjs @@ -1,4 +1,4 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; // The example demonstrates loading multiple assets from a single bundle file @@ -20,9 +20,7 @@ const assets = { assets.bundle.data = { assets: [assets.scene.id, assets.torus.id] }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/loaders/draco-glb.example.mjs b/examples/src/examples/loaders/draco-glb.example.mjs index 70d79ea58e3..0b16e04ff06 100644 --- a/examples/src/examples/loaders/draco-glb.example.mjs +++ b/examples/src/examples/loaders/draco-glb.example.mjs @@ -14,9 +14,7 @@ await new Promise((resolve) => { }); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/loaders/glb.example.mjs b/examples/src/examples/loaders/glb.example.mjs index e20c8ed0033..956f9139ab8 100644 --- a/examples/src/examples/loaders/glb.example.mjs +++ b/examples/src/examples/loaders/glb.example.mjs @@ -12,9 +12,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/loaders/gltf-export.example.mjs b/examples/src/examples/loaders/gltf-export.example.mjs index 08ed1e6e2c7..f7a048fe878 100644 --- a/examples/src/examples/loaders/gltf-export.example.mjs +++ b/examples/src/examples/loaders/gltf-export.example.mjs @@ -48,9 +48,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/loaders/obj.example.mjs b/examples/src/examples/loaders/obj.example.mjs index 0cc4c20f72a..c25e5e4c762 100644 --- a/examples/src/examples/loaders/obj.example.mjs +++ b/examples/src/examples/loaders/obj.example.mjs @@ -5,9 +5,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/loaders/usdz-export.example.mjs b/examples/src/examples/loaders/usdz-export.example.mjs index 59effb2e8fa..3a6ea76205c 100644 --- a/examples/src/examples/loaders/usdz-export.example.mjs +++ b/examples/src/examples/loaders/usdz-export.example.mjs @@ -27,9 +27,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/anisotropy-disc.example.mjs b/examples/src/examples/materials/anisotropy-disc.example.mjs index 9c6d0501a37..65a489d6c67 100644 --- a/examples/src/examples/materials/anisotropy-disc.example.mjs +++ b/examples/src/examples/materials/anisotropy-disc.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/anisotropy-lamp.example.mjs b/examples/src/examples/materials/anisotropy-lamp.example.mjs index 6fef35df79d..8a21342961b 100644 --- a/examples/src/examples/materials/anisotropy-lamp.example.mjs +++ b/examples/src/examples/materials/anisotropy-lamp.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/anisotropy-rotation.example.mjs b/examples/src/examples/materials/anisotropy-rotation.example.mjs index dff06907d57..6ae7ffc95be 100644 --- a/examples/src/examples/materials/anisotropy-rotation.example.mjs +++ b/examples/src/examples/materials/anisotropy-rotation.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/anisotropy-strength.example.mjs b/examples/src/examples/materials/anisotropy-strength.example.mjs index 5f368b9d18f..1f9e09b84b5 100644 --- a/examples/src/examples/materials/anisotropy-strength.example.mjs +++ b/examples/src/examples/materials/anisotropy-strength.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/clear-coat.example.mjs b/examples/src/examples/materials/clear-coat.example.mjs index ab72f4be6c0..5382b304e91 100644 --- a/examples/src/examples/materials/clear-coat.example.mjs +++ b/examples/src/examples/materials/clear-coat.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/dispersion.example.mjs b/examples/src/examples/materials/dispersion.example.mjs index 4f8946150a6..7c0e2241f88 100644 --- a/examples/src/examples/materials/dispersion.example.mjs +++ b/examples/src/examples/materials/dispersion.example.mjs @@ -16,9 +16,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/lit-material.example.mjs b/examples/src/examples/materials/lit-material.example.mjs index a48068ada6b..97697eaed46 100644 --- a/examples/src/examples/materials/lit-material.example.mjs +++ b/examples/src/examples/materials/lit-material.example.mjs @@ -20,9 +20,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/material-anisotropic.example.mjs b/examples/src/examples/materials/material-anisotropic.example.mjs index db59652e400..2f256251a4a 100644 --- a/examples/src/examples/materials/material-anisotropic.example.mjs +++ b/examples/src/examples/materials/material-anisotropic.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/material-clear-coat.example.mjs b/examples/src/examples/materials/material-clear-coat.example.mjs index 29bdc44c618..3cdfe107c26 100644 --- a/examples/src/examples/materials/material-clear-coat.example.mjs +++ b/examples/src/examples/materials/material-clear-coat.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/material-physical.example.mjs b/examples/src/examples/materials/material-physical.example.mjs index 92eb2c38134..17187577e0a 100644 --- a/examples/src/examples/materials/material-physical.example.mjs +++ b/examples/src/examples/materials/material-physical.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/material-translucent-specular.example.mjs b/examples/src/examples/materials/material-translucent-specular.example.mjs index 61ccd509579..c34a284a3b2 100644 --- a/examples/src/examples/materials/material-translucent-specular.example.mjs +++ b/examples/src/examples/materials/material-translucent-specular.example.mjs @@ -15,9 +15,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/materials/material-transparency.example.mjs b/examples/src/examples/materials/material-transparency.example.mjs index 7945f114d73..a3fa3cf724e 100644 --- a/examples/src/examples/materials/material-transparency.example.mjs +++ b/examples/src/examples/materials/material-transparency.example.mjs @@ -10,8 +10,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // disable anti-aliasing to make dithering more pronounced antialias: false, diff --git a/examples/src/examples/materials/normals-and-tangents.example.mjs b/examples/src/examples/materials/normals-and-tangents.example.mjs index 908d3613b6f..500d234a0d1 100644 --- a/examples/src/examples/materials/normals-and-tangents.example.mjs +++ b/examples/src/examples/materials/normals-and-tangents.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/misc/annotations.example.mjs b/examples/src/examples/misc/annotations.example.mjs new file mode 100644 index 00000000000..a419241e6b7 --- /dev/null +++ b/examples/src/examples/misc/annotations.example.mjs @@ -0,0 +1,225 @@ +import { deviceType, rootPath, fileImport } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const { Annotation } = await fileImport(`${rootPath}/static/scripts/esm/annotation.mjs`); +const { CameraControls } = await fileImport(`${rootPath}/static/scripts/esm/camera-controls.mjs`); +const { ShadowCatcher } = await fileImport(`${rootPath}/static/scripts/esm/shadow-catcher.mjs`); + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +// Set up and load draco module, as the glb we load is draco compressed +pc.WasmModule.setConfig('DracoDecoderModule', { + glueUrl: `${rootPath}/static/lib/draco/draco.wasm.js`, + wasmUrl: `${rootPath}/static/lib/draco/draco.wasm.wasm`, + fallbackUrl: `${rootPath}/static/lib/draco/draco.js` +}); + +// Initialize basis to allow loading of compressed textures +pc.basisInitialize({ + glueUrl: `${rootPath}/static/lib/basis/basis.wasm.js`, + wasmUrl: `${rootPath}/static/lib/basis/basis.wasm.wasm`, + fallbackUrl: `${rootPath}/static/lib/basis/basis.js` +}); + +const assets = { + jetFighter: new pc.Asset('jet-fighter', 'container', { url: `${rootPath}/static/assets/models/jet-fighter.glb` }), + shanghai: new pc.Asset( + 'shanghai', + 'texture', + { url: `${rootPath}/static/assets/hdri/shanghai-riverside-4k.hdr` }, + { mipmaps: false } + ) +}; + +const gfxOptions = { + deviceTypes: [deviceType] +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.mouse = new pc.Mouse(document.body); +createOptions.touch = new pc.TouchDevice(document.body); + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + // Setup HDR environment + const applyHdri = (source) => { + const skybox = pc.EnvLighting.generateSkyboxCubemap(source); + app.scene.skybox = skybox; + + const lighting = pc.EnvLighting.generateLightingSource(source); + const envAtlas = pc.EnvLighting.generateAtlas(lighting); + lighting.destroy(); + app.scene.envAtlas = envAtlas; + }; + + device.on('devicerestored', () => { + applyHdri(assets.shanghai.resource); + }); + + applyHdri(assets.shanghai.resource); + + // Setup sky dome + app.scene.sky.type = pc.SKYTYPE_DOME; + app.scene.sky.node.setLocalScale(new pc.Vec3(50, 50, 50)); + app.scene.sky.node.setLocalPosition(new pc.Vec3(0, 0, 0)); + app.scene.sky.center = new pc.Vec3(0, 0.1, 0); + + // Create camera entity + const camera = new pc.Entity('camera'); + camera.addComponent('camera', { + clearColor: new pc.Color(0.5, 0.6, 0.9), + farClip: 500, + toneMapping: pc.TONEMAP_ACES2 + }); + camera.setPosition(0, 1.75, 8); + + // Add camera controls + camera.addComponent('script'); + camera.script.create(CameraControls, { + properties: { + focusPoint: new pc.Vec3(0, 1.75, 0), + pitchRange: new pc.Vec2(-90, 0), + sceneSize: 2, + zoomRange: new pc.Vec2(5, 25) + } + }); + + app.root.addChild(camera); + + // Create directional light + const light = new pc.Entity('light'); + light.addComponent('light', { + type: 'directional', + castShadows: true, + shadowDistance: 30, + shadowIntensity: 0.6, + shadowResolution: 1024, + shadowType: pc.SHADOW_VSM_16F + }); + app.root.addChild(light); + + // Create a wrapper entity for the jet fighter (like pc-model does in web-components) + const jetFighter = new pc.Entity('jet-fighter'); + jetFighter.setPosition(-2, 1.6, 0); + jetFighter.setEulerAngles(0, 0, 3); + app.root.addChild(jetFighter); + + // Instantiate the model as a child of the wrapper + const jetModel = assets.jetFighter.resource.instantiateRenderEntity({ + castShadows: true + }); + jetFighter.addChild(jetModel); + + /** + * Create an annotation entity + * @param {pc.Vec3} position - Position relative to parent + * @param {string} label - Label number + * @param {string} title - Annotation title + * @param {string} text - Annotation description + * @returns {pc.Entity} The annotation entity + */ + const createAnnotation = (position, label, title, text) => { + const entity = new pc.Entity(`annotation${label}`); + entity.setLocalPosition(position); + entity.addComponent('script'); + entity.script.create(Annotation, { + properties: { + label: label, + title: title, + text: text + } + }); + return entity; + }; + + // Add annotations to the jet fighter + jetFighter.addChild(createAnnotation( + new pc.Vec3(5.5, 1.2, 0), + '1', + 'Cockpit Canopy', + 'Transparent canopy offering visibility and housing the pilot\'s controls.' + )); + + jetFighter.addChild(createAnnotation( + new pc.Vec3(8, 0.25, 0), + '2', + 'Nose Cone & Radar', + 'Houses the advanced radar system for targeting and navigation.' + )); + + jetFighter.addChild(createAnnotation( + new pc.Vec3(5, -0.5, 0), + '3', + 'Inlet Ducts', + 'Provides airflow to the engines, crucial for maintaining thrust.' + )); + + jetFighter.addChild(createAnnotation( + new pc.Vec3(0.5, 0, 5.1), + '4', + 'Wingtip Missile Rails', + 'Can be equipped with AIM-9 Sidewinder missiles for air-to-air combat.' + )); + + jetFighter.addChild(createAnnotation( + new pc.Vec3(-4, 0, 0), + '5', + 'Jet Engine Nozzles', + 'Dual afterburning turbofan engines for high-speed performance.' + )); + + jetFighter.addChild(createAnnotation( + new pc.Vec3(1, -1, -1), + '6', + 'Main Landing Gear', + 'Retractable gear for safe takeoff and landing on runways.' + )); + + jetFighter.addChild(createAnnotation( + new pc.Vec3(2, 0, -3.1), + '7', + 'Forward Leading-Edge Flaps', + 'Enhance maneuverability during high-speed or low-speed flight.' + )); + + // Create shadow catcher + const shadowCatcher = new pc.Entity('shadowCatcher'); + shadowCatcher.addComponent('script'); + shadowCatcher.script.create(ShadowCatcher, { + properties: { + scale: new pc.Vec3(15, 15, 15) + } + }); + app.root.addChild(shadowCatcher); +}); + +export { app }; diff --git a/examples/src/examples/misc/editor.example.mjs b/examples/src/examples/misc/editor.example.mjs index c123b85878d..40c0e889c71 100644 --- a/examples/src/examples/misc/editor.example.mjs +++ b/examples/src/examples/misc/editor.example.mjs @@ -14,9 +14,7 @@ const { GizmoHandler } = await localImport('gizmo-handler.mjs'); const { Selector } = await localImport('selector.mjs'); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/misc/esm-script.example.mjs b/examples/src/examples/misc/esm-script.example.mjs index dbd9bc15300..f33026b3483 100644 --- a/examples/src/examples/misc/esm-script.example.mjs +++ b/examples/src/examples/misc/esm-script.example.mjs @@ -8,9 +8,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/misc/hello-world.example.mjs b/examples/src/examples/misc/hello-world.example.mjs index 97284f225a2..c787958a427 100644 --- a/examples/src/examples/misc/hello-world.example.mjs +++ b/examples/src/examples/misc/hello-world.example.mjs @@ -1,13 +1,11 @@ -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/misc/mini-stats.example.mjs b/examples/src/examples/misc/mini-stats.example.mjs index bd705f5550b..33303524ab8 100644 --- a/examples/src/examples/misc/mini-stats.example.mjs +++ b/examples/src/examples/misc/mini-stats.example.mjs @@ -1,16 +1,14 @@ // @config ENGINE performance // @config NO_MINISTATS // @config WEBGPU_DISABLED -import { deviceType, rootPath } from 'examples/utils'; +import { deviceType } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/misc/spineboy.example.mjs b/examples/src/examples/misc/spineboy.example.mjs index 4c2cf4d56d1..9ec391d9af8 100644 --- a/examples/src/examples/misc/spineboy.example.mjs +++ b/examples/src/examples/misc/spineboy.example.mjs @@ -14,9 +14,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/physics/compound-collision.example.mjs b/examples/src/examples/physics/compound-collision.example.mjs index 96abdfc4f42..dfe23bae097 100644 --- a/examples/src/examples/physics/compound-collision.example.mjs +++ b/examples/src/examples/physics/compound-collision.example.mjs @@ -14,9 +14,7 @@ await new Promise((resolve) => { }); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/physics/falling-shapes.example.mjs b/examples/src/examples/physics/falling-shapes.example.mjs index d59b941efb3..9398f9beddb 100644 --- a/examples/src/examples/physics/falling-shapes.example.mjs +++ b/examples/src/examples/physics/falling-shapes.example.mjs @@ -18,9 +18,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/physics/offset-collision.example.mjs b/examples/src/examples/physics/offset-collision.example.mjs index 58ac3385712..6145ed8c7eb 100644 --- a/examples/src/examples/physics/offset-collision.example.mjs +++ b/examples/src/examples/physics/offset-collision.example.mjs @@ -25,9 +25,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/physics/raycast.example.mjs b/examples/src/examples/physics/raycast.example.mjs index f5b275676f0..9558c431b38 100644 --- a/examples/src/examples/physics/raycast.example.mjs +++ b/examples/src/examples/physics/raycast.example.mjs @@ -18,9 +18,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/physics/vehicle.example.mjs b/examples/src/examples/physics/vehicle.example.mjs index fed03c8ee83..c8a58f49571 100644 --- a/examples/src/examples/physics/vehicle.example.mjs +++ b/examples/src/examples/physics/vehicle.example.mjs @@ -28,9 +28,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/grab-pass.example.mjs b/examples/src/examples/shaders/grab-pass.example.mjs index 6bd84ed5250..9c3ad8e3c0f 100644 --- a/examples/src/examples/shaders/grab-pass.example.mjs +++ b/examples/src/examples/shaders/grab-pass.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/ground-fog.example.mjs b/examples/src/examples/shaders/ground-fog.example.mjs index 75a710b94a5..07167865fae 100644 --- a/examples/src/examples/shaders/ground-fog.example.mjs +++ b/examples/src/examples/shaders/ground-fog.example.mjs @@ -19,9 +19,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/integer-textures.example.mjs b/examples/src/examples/shaders/integer-textures.example.mjs index 83adff46d9f..eec5f4c67b7 100644 --- a/examples/src/examples/shaders/integer-textures.example.mjs +++ b/examples/src/examples/shaders/integer-textures.example.mjs @@ -43,9 +43,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -146,7 +144,8 @@ const outputShader = pc.ShaderUtils.createShader(device, { uniqueName: 'RenderOutputShader', attributes: { aPosition: pc.SEMANTIC_POSITION }, vertexChunk: 'quadVS', - fragmentGLSL: files['renderOutput.frag'] + fragmentGLSL: files['renderOutput.glsl.frag'], + fragmentWGSL: files['renderOutput.wgsl.frag'] // For the output shader, we don't need to specify the output type, // as we are returning a vec4 by default. }); diff --git a/examples/src/examples/shaders/integer-textures.renderOutput.frag b/examples/src/examples/shaders/integer-textures.renderOutput.glsl.frag similarity index 100% rename from examples/src/examples/shaders/integer-textures.renderOutput.frag rename to examples/src/examples/shaders/integer-textures.renderOutput.glsl.frag diff --git a/examples/src/examples/shaders/integer-textures.renderOutput.wgsl.frag b/examples/src/examples/shaders/integer-textures.renderOutput.wgsl.frag new file mode 100644 index 00000000000..ab8c72cdb34 --- /dev/null +++ b/examples/src/examples/shaders/integer-textures.renderOutput.wgsl.frag @@ -0,0 +1,112 @@ +// Texture (unsigned-integer, fetch-only) +var sourceTexture: texture_2d; + +// Uniforms (auto-buffered, accessed as uniform.) +uniform mousePosition: vec2f; +uniform brushRadius: f32; + +// Interpolated varying (from vertex shader) +varying uv0: vec2f; + +// Color constants +const whiteColor: vec3f = vec3f(1.0); +const skyBlueColor: vec3f = vec3f(0.2, 0.2, 0.2); +const yellowSandColor: vec3f = vec3f(0.73, 0.58, 0.26); +const orangeSandColor: vec3f = vec3f(0.87, 0.43, 0.22); +const graySandColor: vec3f = vec3f(0.13, 0.16, 0.17); +const grayWallColor: vec3f = vec3f(0.5, 0.5, 0.5); +const waterBlueColor: vec3f = vec3f(0.2, 0.3, 0.8); + +// Particle element constants +const AIR: u32 = 0u; +const SAND: u32 = 1u; +const ORANGESAND: u32 = 2u; +const GRAYSAND: u32 = 3u; +const WALL: u32 = 4u; + +// Circle distance function +fn circle(p: vec2f, r: f32) -> f32 { + return length(p) - r; +} + +const circleOutline: f32 = 0.0025; + +// Helper: check bounds in integer texel space +fn isInBounds(c: vec2i, size: vec2i) -> bool { + return (c.x > 0 && c.x < size.x - 1) && + (c.y > 0 && c.y < size.y - 1); +} + +// Particle representation +struct Particle { + element: u32, + movedThisFrame: bool, + shade: u32, + waterMass: u32 // unused here +}; + +// Pseudo-random generator +fn rand(pos: vec2f, val: f32) -> f32 { + return fract(pos.x * pos.y * val * 1000.0); +} + +// Pack a Particle into a single u32 +fn pack(p: Particle) -> u32 { + var packed: u32 = 0u; + packed |= (p.element & 0x7u); + packed |= u32(p.movedThisFrame) << 3; + packed |= ((p.shade & 0xFu) << 4); + return packed; +} + +// Unpack a u32 into a Particle +fn unpack(packed: u32) -> Particle { + var pt: Particle; + pt.element = packed & 0x7u; + pt.movedThisFrame = ((packed >> 3) & 0x1u) != 0u; + pt.shade = (packed >> 4) & 0xFu; + pt.waterMass = 0u; + return pt; +} + +// Fetch and decode a particle from the texture +fn getParticle(coord: vec2i) -> Particle { + let texel: vec4 = textureLoad(sourceTexture, coord, 0); + return unpack(texel.x); +} + +@fragment +fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + + // Determine integer texture size & sample coordinate + let dims: vec2u = textureDimensions(sourceTexture); + let size: vec2i = vec2i(dims); + let coord: vec2i = vec2i(input.uv0 * vec2f(size)); + + let particle = getParticle(coord); + + var gameColor: vec3f = skyBlueColor; + if (particle.element == SAND) { + gameColor = mix(yellowSandColor, whiteColor, (f32(particle.shade) / 15.0) * 0.5); + } else if (particle.element == WALL) { + gameColor = grayWallColor; + } else if (particle.element == ORANGESAND) { + gameColor = mix(orangeSandColor, whiteColor, (f32(particle.shade) / 15.0) * 0.5); + } else if (particle.element == GRAYSAND) { + gameColor = mix(graySandColor, whiteColor, (f32(particle.shade) / 15.0) * 0.5); + } + + // Render a brush circle + let d: f32 = length(input.uv0 - uniform.mousePosition); + let wd: f32 = fwidth(d); + let circleVal: f32 = smoothstep(uniform.brushRadius + wd, uniform.brushRadius, d); + let circleInner: f32 = smoothstep(uniform.brushRadius - circleOutline + wd, uniform.brushRadius - circleOutline, d); + let brush: f32 = max(circleVal - circleInner, 0.0) * 0.5; + + let outColor: vec3f = mix(gameColor, vec3f(1.0), brush); + + output.color = vec4f(outColor, 1.0); + return output; +} + diff --git a/examples/src/examples/shaders/paint-mesh.example.mjs b/examples/src/examples/shaders/paint-mesh.example.mjs index 60727746662..ff624be6548 100644 --- a/examples/src/examples/shaders/paint-mesh.example.mjs +++ b/examples/src/examples/shaders/paint-mesh.example.mjs @@ -18,9 +18,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/shader-burn.example.mjs b/examples/src/examples/shaders/shader-burn.example.mjs index 870ae7a7106..8d2aee4f375 100644 --- a/examples/src/examples/shaders/shader-burn.example.mjs +++ b/examples/src/examples/shaders/shader-burn.example.mjs @@ -12,8 +12,6 @@ const assets = { const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, // Enable HDR rendering if supported displayFormat: pc.DISPLAYFORMAT_HDR diff --git a/examples/src/examples/shaders/shader-hatch.example.mjs b/examples/src/examples/shaders/shader-hatch.example.mjs index 800012b959f..976f5edf468 100644 --- a/examples/src/examples/shaders/shader-hatch.example.mjs +++ b/examples/src/examples/shaders/shader-hatch.example.mjs @@ -33,9 +33,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const createOptions = new pc.AppOptions(); diff --git a/examples/src/examples/shaders/shader-toon.example.mjs b/examples/src/examples/shaders/shader-toon.example.mjs index 4247af9e1ff..04769e8feb1 100644 --- a/examples/src/examples/shaders/shader-toon.example.mjs +++ b/examples/src/examples/shaders/shader-toon.example.mjs @@ -10,9 +10,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/shader-wobble.example.mjs b/examples/src/examples/shaders/shader-wobble.example.mjs index 3e9c2aa4074..6dc437dc5ba 100644 --- a/examples/src/examples/shaders/shader-wobble.example.mjs +++ b/examples/src/examples/shaders/shader-wobble.example.mjs @@ -10,9 +10,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/texture-array.example.mjs b/examples/src/examples/shaders/texture-array.example.mjs index 42932b60c70..e09234427ed 100644 --- a/examples/src/examples/shaders/texture-array.example.mjs +++ b/examples/src/examples/shaders/texture-array.example.mjs @@ -54,9 +54,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/shaders/trees.example.mjs b/examples/src/examples/shaders/trees.example.mjs index 2296000acb5..ab740845dab 100644 --- a/examples/src/examples/shaders/trees.example.mjs +++ b/examples/src/examples/shaders/trees.example.mjs @@ -1,5 +1,5 @@ // @config DESCRIPTION
This example shows how to override shader chunks of StandardMaterial.
-import { deviceType, rootPath } from 'examples/utils'; +import { deviceType, rootPath, localImport } from 'examples/utils'; import * as pc from 'playcanvas'; const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); @@ -10,14 +10,17 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); +// Determine shader language and import the appropriate shader chunks +const shaderLanguage = device.isWebGPU ? pc.SHADERLANGUAGE_WGSL : pc.SHADERLANGUAGE_GLSL; +const shaderChunkFile = device.isWebGPU ? 'shader-chunks.wgsl.mjs' : 'shader-chunks.glsl.mjs'; +const shaderChunks = await localImport(shaderChunkFile); + const createOptions = new pc.AppOptions(); createOptions.graphicsDevice = device; @@ -105,94 +108,20 @@ assetListLoader.load(() => { const meshInstance = forest.findComponent('render').meshInstances[0]; meshInstance.setInstancing(vertexBuffer); - // ------ Shader chunks to override StandardMaterial default behavior ------ - - // a fragment chunk to add few uniforms - const litUserDeclarationPS = /* glsl */ ` - uniform float myTime; - uniform vec2 myFogParams; - `; - - // override existing diffuse fragment chunk to simply blend between two colors based on time - const diffusePS = /* glsl */ ` - void getAlbedo() { - float blend = 0.5 + 0.5 * sin(myTime * 0.5); - vec3 green = vec3(0.2, 1.0, 0.0); - vec3 orange = vec3(1.0, 0.2, 0.0); - dAlbedo = mix(green, orange, blend); - } - `; - - // a fragment chunk with runs at the end of the main function of the shader, to apply ground fog - const litUserMainEndPS = /* glsl */ ` - vec3 fogColor = vec3(1.0, 1.0, 1.0); - float fogStart = myFogParams.x; - float fogEnd = myFogParams.y; - - // Compute fog amount based on height - float fogFactor = clamp((vPositionW.y - fogStart) / (fogEnd - fogStart), 0.0, 1.0); - gl_FragColor.rgb = mix(fogColor, gl_FragColor.rgb, fogFactor); - `; - - // a vertex shader chunk to customize the code generating vertex position in local (object) space. - // The vertex position is adjusted to sway in the wind based. Note that some parts of the original - // chunk were removed, and only parts relevant to instancing were kept, as that's what is needed here. - const transformCoreVS = /* glsl */ ` - - uniform float myTime; // add time uniform to vertex shader - - // these are existing attributes and uniforms - attribute vec4 vertex_position; - uniform mat4 matrix_viewProjection; - uniform mat4 matrix_model; - - #if defined(INSTANCING) - #include "transformInstancingVS" - #endif - - // provide a replacement function here to do the actual work, instead of simply returning the vertexPosition - vec3 getLocalPosition(vec3 vertexPosition) { - // Extract the position (translation) from the model matrix - this is the position of the instance of the tree - vec3 treePosition = getModelMatrix()[3].xyz; - - // and use it to generate a random seed for the sway, so all trees are not synchronized - float randomSeed = treePosition.x * 0.1 + treePosition.z * 0.5; - - // Height-based sway factor (0 at base, 1 at top). Note that the pivot point of the tree is not at the base, - // so compensate for that. - float heightFromBase = vertexPosition.y + 4.5; - float maxSwayHeight = 9.0; - float swayFactor = clamp(heightFromBase / maxSwayHeight, 0.0, 1.0); - - // Parameters - could be exposed as uniforms - float swayStrength = 0.3; - float swaySpeed = 2.0; - - // sway the tree - vec3 localPos = vertexPosition; - float bendOffset = sin(myTime * swaySpeed + randomSeed); - localPos.x += bendOffset * swayFactor * heightFromBase * swayStrength; - - return localPos; - } - `; - - // ------ End of shader chunks ------ - - // apply all these chunks to the tree material - const treeChunksGLSL = meshInstance.material.getShaderChunks(pc.SHADERLANGUAGE_GLSL); - treeChunksGLSL.set('diffusePS', diffusePS); - treeChunksGLSL.set('litUserMainEndPS', litUserMainEndPS); - treeChunksGLSL.set('litUserDeclarationPS', litUserDeclarationPS); - treeChunksGLSL.set('transformCoreVS', transformCoreVS); + // apply shader chunks to the tree material + const treeChunks = meshInstance.material.getShaderChunks(shaderLanguage); + treeChunks.add(shaderChunks); meshInstance.material.shaderChunksVersion = '2.8'; // create a ground material - all chunks apart from swaying in the wind, so fog and color blending const groundMaterial = new pc.StandardMaterial(); - const groundChunksGLSL = groundMaterial.getShaderChunks(pc.SHADERLANGUAGE_GLSL); - groundChunksGLSL.set('diffusePS', diffusePS); - groundChunksGLSL.set('litUserMainEndPS', litUserMainEndPS); - groundChunksGLSL.set('litUserDeclarationPS', litUserDeclarationPS); + const groundChunks = groundMaterial.getShaderChunks(shaderLanguage); + // only add the chunks we need (excluding transformCoreVS which is for tree swaying) + groundChunks.add({ + diffusePS: shaderChunks.diffusePS, + litUserMainEndPS: shaderChunks.litUserMainEndPS, + litUserDeclarationPS: shaderChunks.litUserDeclarationPS + }); groundMaterial.shaderChunksVersion = '2.8'; const ground = new pc.Entity('Ground'); diff --git a/examples/src/examples/shaders/trees.shader-chunks.glsl.mjs b/examples/src/examples/shaders/trees.shader-chunks.glsl.mjs new file mode 100644 index 00000000000..ca4af093661 --- /dev/null +++ b/examples/src/examples/shaders/trees.shader-chunks.glsl.mjs @@ -0,0 +1,72 @@ +/** + * GLSL shader chunks for the trees example. + * These chunks override StandardMaterial default behavior to create animated trees with fog. + */ + +// Fragment chunk to add custom uniforms +export const litUserDeclarationPS = /* glsl */ ` + uniform float myTime; + uniform vec2 myFogParams; +`; + +// Override existing diffuse fragment chunk to blend between two colors based on time +export const diffusePS = /* glsl */ ` +void getAlbedo() { + float blend = 0.5 + 0.5 * sin(myTime * 0.5); + vec3 green = vec3(0.2, 1.0, 0.0); + vec3 orange = vec3(1.0, 0.2, 0.0); + dAlbedo = mix(green, orange, blend); +} +`; + +// Fragment chunk that runs at the end of the main function to apply ground fog +export const litUserMainEndPS = /* glsl */ ` + vec3 fogColor = vec3(1.0, 1.0, 1.0); + float fogStart = myFogParams.x; + float fogEnd = myFogParams.y; + + // Compute fog amount based on height + float fogFactor = clamp((vPositionW.y - fogStart) / (fogEnd - fogStart), 0.0, 1.0); + gl_FragColor.rgb = mix(fogColor, gl_FragColor.rgb, fogFactor); +`; + +// Vertex shader chunk to customize vertex position with wind sway animation +export const transformCoreVS = /* glsl */ ` + + uniform float myTime; // add time uniform to vertex shader + + // these are existing attributes and uniforms + attribute vec4 vertex_position; + uniform mat4 matrix_viewProjection; + uniform mat4 matrix_model; + + #if defined(INSTANCING) + #include "transformInstancingVS" + #endif + + // provide a replacement function here to do the actual work, instead of simply returning the vertexPosition + vec3 getLocalPosition(vec3 vertexPosition) { + // Extract the position (translation) from the model matrix - this is the position of the instance of the tree + vec3 treePosition = getModelMatrix()[3].xyz; + + // and use it to generate a random seed for the sway, so all trees are not synchronized + float randomSeed = treePosition.x * 0.1 + treePosition.z * 0.5; + + // Height-based sway factor (0 at base, 1 at top). Note that the pivot point of the tree is not at the base, + // so compensate for that. + float heightFromBase = vertexPosition.y + 4.5; + float maxSwayHeight = 9.0; + float swayFactor = clamp(heightFromBase / maxSwayHeight, 0.0, 1.0); + + // Parameters - could be exposed as uniforms + float swayStrength = 0.3; + float swaySpeed = 2.0; + + // sway the tree + vec3 localPos = vertexPosition; + float bendOffset = sin(myTime * swaySpeed + randomSeed); + localPos.x += bendOffset * swayFactor * heightFromBase * swayStrength; + + return localPos; + } +`; diff --git a/examples/src/examples/shaders/trees.shader-chunks.wgsl.mjs b/examples/src/examples/shaders/trees.shader-chunks.wgsl.mjs new file mode 100644 index 00000000000..4750c40820c --- /dev/null +++ b/examples/src/examples/shaders/trees.shader-chunks.wgsl.mjs @@ -0,0 +1,72 @@ +/** + * WGSL shader chunks for the trees example. + * These chunks override StandardMaterial default behavior to create animated trees with fog. + */ + +// Fragment chunk to add custom uniforms +export const litUserDeclarationPS = /* wgsl */ ` + uniform myTime: f32; + uniform myFogParams: vec2f; +`; + +// Override existing diffuse fragment chunk to blend between two colors based on time +export const diffusePS = /* wgsl */ ` +fn getAlbedo() { + let blend: f32 = 0.5 + 0.5 * sin(uniform.myTime * 0.5); + let green: vec3f = vec3f(0.2, 1.0, 0.0); + let orange: vec3f = vec3f(1.0, 0.2, 0.0); + dAlbedo = mix(green, orange, blend); +} +`; + +// Fragment chunk that runs at the end of the main function to apply ground fog +export const litUserMainEndPS = /* wgsl */ ` + let fogColor: vec3f = vec3f(1.0, 1.0, 1.0); + let fogStart: f32 = uniform.myFogParams.x; + let fogEnd: f32 = uniform.myFogParams.y; + + // Compute fog amount based on height + let fogFactor: f32 = clamp((vPositionW.y - fogStart) / (fogEnd - fogStart), 0.0, 1.0); + output.color = vec4f(mix(fogColor, output.color.rgb, fogFactor), output.color.a); +`; + +// Vertex shader chunk to customize vertex position with wind sway animation +export const transformCoreVS = /* wgsl */ ` + + uniform myTime: f32; // add time uniform to vertex shader + + // these are existing attributes and uniforms + attribute vertex_position: vec4f; + uniform matrix_viewProjection: mat4x4f; + uniform matrix_model: mat4x4f; + + #if defined(INSTANCING) + #include "transformInstancingVS" + #endif + + // provide a replacement function here to do the actual work, instead of simply returning the vertexPosition + fn getLocalPosition(vertexPosition: vec3f) -> vec3f { + // Extract the position (translation) from the model matrix - this is the position of the instance of the tree + let treePosition: vec3f = getModelMatrix()[3].xyz; + + // and use it to generate a random seed for the sway, so all trees are not synchronized + let randomSeed: f32 = treePosition.x * 0.1 + treePosition.z * 0.5; + + // Height-based sway factor (0 at base, 1 at top). Note that the pivot point of the tree is not at the base, + // so compensate for that. + let heightFromBase: f32 = vertexPosition.y + 4.5; + let maxSwayHeight: f32 = 9.0; + let swayFactor: f32 = clamp(heightFromBase / maxSwayHeight, 0.0, 1.0); + + // Parameters - could be exposed as uniforms + let swayStrength: f32 = 0.3; + let swaySpeed: f32 = 2.0; + + // sway the tree + var localPos: vec3f = vertexPosition; + let bendOffset: f32 = sin(uniform.myTime * swaySpeed + randomSeed); + localPos.x = localPos.x + bendOffset * swayFactor * heightFromBase * swayStrength; + + return localPos; + } +`; diff --git a/examples/src/examples/shaders/wgsl-shader.example.mjs b/examples/src/examples/shaders/wgsl-shader.example.mjs index bab7f5e4016..d970df92536 100644 --- a/examples/src/examples/shaders/wgsl-shader.example.mjs +++ b/examples/src/examples/shaders/wgsl-shader.example.mjs @@ -14,9 +14,7 @@ const assets = { // Even though we're using WGSL, we still need to provide glslang // and twgsl to compile shaders used internally by the engine. const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/sound/positional.example.mjs b/examples/src/examples/sound/positional.example.mjs index 00f3b6289e7..1df6057dee7 100644 --- a/examples/src/examples/sound/positional.example.mjs +++ b/examples/src/examples/sound/positional.example.mjs @@ -5,9 +5,7 @@ const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('applic window.focus(); const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/attenuation.example.mjs b/examples/src/examples/test/attenuation.example.mjs new file mode 100644 index 00000000000..bffdf1c4851 --- /dev/null +++ b/examples/src/examples/test/attenuation.example.mjs @@ -0,0 +1,96 @@ +// @config HIDDEN +import { deviceType, rootPath } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +const assets = { + orbitCamera: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }), + helipad: new pc.Asset( + 'helipad-env-atlas', + 'texture', + { url: `${rootPath}/static/assets/cubemaps/morning-env-atlas.png` }, + { type: pc.TEXTURETYPE_RGBP, mipmaps: false } + ), + model: new pc.Asset('model', 'container', { url: `${rootPath}/static/assets/models/AttenuationTest.glb` }) +}; + +const gfxOptions = { + deviceTypes: [deviceType] +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; +createOptions.mouse = new pc.Mouse(document.body); +createOptions.touch = new pc.TouchDevice(document.body); +createOptions.keyboard = new pc.Keyboard(document.body); + +createOptions.componentSystems = [ + pc.RenderComponentSystem, + pc.CameraComponentSystem, + pc.LightComponentSystem, + pc.ScriptComponentSystem +]; +createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); + +// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +// Ensure canvas is resized when window changes size +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); +assetListLoader.load(() => { + app.start(); + + const leftEntity = assets.model.resource.instantiateRenderEntity(); + leftEntity.setLocalEulerAngles(0, 90, 0); + leftEntity.setPosition(0, 0, 1); + leftEntity.setLocalScale(0.8, 0.8, 0.8); + app.root.addChild(leftEntity); + + // Create a camera with an orbit camera script + const camera = new pc.Entity(); + camera.addComponent('camera', { + toneMapping: pc.TONEMAP_LINEAR + }); + camera.camera.requestSceneColorMap(true); + camera.addComponent('script'); + camera.script.create('orbitCamera', { + attributes: { + inertiaFactor: 0.2 + } + }); + camera.script.create('orbitCameraInputMouse'); + camera.script.create('orbitCameraInputTouch'); + app.root.addChild(camera); + camera.script.orbitCamera.yaw = 90; + camera.script.orbitCamera.distance = 24; + + // test with camera frame which uses linear rendering + const cameraFrame = false; + + if (cameraFrame) { + const cameraFrame = new pc.CameraFrame(app, camera.camera); + cameraFrame.rendering.samples = 4; + cameraFrame.rendering.toneMapping = pc.TONEMAP_LINEAR; + cameraFrame.rendering.sceneColorMap = true; + cameraFrame.update(); + } + + app.scene.ambientLight = new pc.Color(0.9, 0.9, 0.9); +}); + +export { app }; diff --git a/examples/src/examples/test/contact-hardening-shadows.example.mjs b/examples/src/examples/test/contact-hardening-shadows.example.mjs index 26431939b3e..78c3067bf28 100644 --- a/examples/src/examples/test/contact-hardening-shadows.example.mjs +++ b/examples/src/examples/test/contact-hardening-shadows.example.mjs @@ -31,9 +31,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/detail-map.example.mjs b/examples/src/examples/test/detail-map.example.mjs index 26ce3420f18..ceedd2ddef3 100644 --- a/examples/src/examples/test/detail-map.example.mjs +++ b/examples/src/examples/test/detail-map.example.mjs @@ -23,9 +23,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/global-shader-properties.example.mjs b/examples/src/examples/test/global-shader-properties.example.mjs index 9d9ce7b39c9..688430c7879 100644 --- a/examples/src/examples/test/global-shader-properties.example.mjs +++ b/examples/src/examples/test/global-shader-properties.example.mjs @@ -20,9 +20,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -125,6 +123,18 @@ assetListLoader.load(() => { litArgs_ao = 0.0; litArgs_opacity = 1.0; }`; + material.shaderChunkWGSL = ` + #include "litShaderCorePS" + fn evaluateFrontend() { + litArgs_emission = vec3f(0.7, 0.4, 0); + litArgs_metalness = 0.5; + litArgs_specularity = vec3f(0.5, 0.5, 0.5); + litArgs_specularityFactor = 1.0; + litArgs_gloss = 0.5; + litArgs_ior = 0.1; + litArgs_ao = 0.0; + litArgs_opacity = 1.0; + }`; material.update(); // create primitive diff --git a/examples/src/examples/test/material-test.example.mjs b/examples/src/examples/test/material-test.example.mjs index 315c9eadca2..4756886f192 100644 --- a/examples/src/examples/test/material-test.example.mjs +++ b/examples/src/examples/test/material-test.example.mjs @@ -21,9 +21,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/opacity.example.mjs b/examples/src/examples/test/opacity.example.mjs index ea348bdd612..f42279f8a56 100644 --- a/examples/src/examples/test/opacity.example.mjs +++ b/examples/src/examples/test/opacity.example.mjs @@ -16,9 +16,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/parallax-mapping.example.mjs b/examples/src/examples/test/parallax-mapping.example.mjs index 037e5bbbbf5..ba92f809932 100644 --- a/examples/src/examples/test/parallax-mapping.example.mjs +++ b/examples/src/examples/test/parallax-mapping.example.mjs @@ -20,9 +20,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/primitive-mode.example.mjs b/examples/src/examples/test/primitive-mode.example.mjs index ed9514cc75c..250a3fd1713 100644 --- a/examples/src/examples/test/primitive-mode.example.mjs +++ b/examples/src/examples/test/primitive-mode.example.mjs @@ -18,9 +18,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/test/radix-sort.controls.mjs b/examples/src/examples/test/radix-sort.controls.mjs new file mode 100644 index 00000000000..01bca698c97 --- /dev/null +++ b/examples/src/examples/test/radix-sort.controls.mjs @@ -0,0 +1,38 @@ +/** + * @param {import('../../app/components/Example.mjs').ControlOptions} options - The options. + * @returns {JSX.Element} The returned JSX Element. + */ +export const controls = ({ observer, ReactPCUI, jsx, fragment }) => { + const { BindingTwoWay, LabelGroup, Panel, SliderInput } = ReactPCUI; + + return fragment( + jsx( + Panel, + { headerText: 'Radix Sort' }, + jsx( + LabelGroup, + { text: 'Elements (K)' }, + jsx(SliderInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'options.elementsK' }, + value: 1000, + min: 1, + max: 10000, + precision: 0 + }) + ), + jsx( + LabelGroup, + { text: 'Bits' }, + jsx(SliderInput, { + binding: new BindingTwoWay(), + link: { observer, path: 'options.bits' }, + value: 16, + min: 4, + max: 24, + precision: 0 + }) + ) + ) + ); +}; diff --git a/examples/src/examples/test/radix-sort.example.mjs b/examples/src/examples/test/radix-sort.example.mjs new file mode 100644 index 00000000000..2cb8ab9fab7 --- /dev/null +++ b/examples/src/examples/test/radix-sort.example.mjs @@ -0,0 +1,334 @@ +// @config DESCRIPTION Test example for RenderPassRadixSort - GPU radix sort using mipmap binary search +// @config HIDDEN +import files from 'examples/files'; +import { data } from 'examples/observer'; +import { deviceType, rootPath } from 'examples/utils'; +import * as pc from 'playcanvas'; + +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); +window.focus(); + +const gfxOptions = { + deviceTypes: [deviceType], + glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, + twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` +}; + +const device = await pc.createGraphicsDevice(canvas, gfxOptions); +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); + +const createOptions = new pc.AppOptions(); +createOptions.graphicsDevice = device; + +createOptions.componentSystems = [pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem]; +createOptions.resourceHandlers = [pc.TextureHandler]; + +const app = new pc.AppBase(canvas); +app.init(createOptions); +app.start(); + +app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); +app.setCanvasResolution(pc.RESOLUTION_AUTO); + +const resize = () => app.resizeCanvas(); +window.addEventListener('resize', resize); +app.on('destroy', () => { + window.removeEventListener('resize', resize); +}); + +// State +/** @type {number} */ +let currentNumElements = 1000 * 1000; // 1M default +/** @type {number} */ +let currentNumBits = 16; +/** @type {pc.Texture|null} */ +let keysTexture = null; +/** @type {pc.RenderPassRadixSort|null} */ +let radixSort = null; +/** @type {number[]} */ +let originalValues = []; +/** @type {boolean} */ +let needsRegen = true; + +// Create render pass instance once +radixSort = new pc.RenderPassRadixSort(device); + +// ==================== MATERIALS ==================== + +// Create unsorted visualization material +const unsortedMaterial = new pc.ShaderMaterial({ + uniqueName: 'UnsortedVizMaterial', + vertexGLSL: files['vert.glsl'], + fragmentGLSL: files['unsorted.glsl.frag'], + vertexWGSL: files['vert.wgsl'], + fragmentWGSL: files['unsorted.wgsl.frag'], + attributes: { + aPosition: pc.SEMANTIC_POSITION, + aUv0: pc.SEMANTIC_TEXCOORD0 + } +}); + +// Create sorted visualization material +const sortedMaterial = new pc.ShaderMaterial({ + uniqueName: 'SortedVizMaterial', + vertexGLSL: files['vert.glsl'], + fragmentGLSL: files['sorted.glsl.frag'], + vertexWGSL: files['vert.wgsl'], + fragmentWGSL: files['sorted.wgsl.frag'], + attributes: { + aPosition: pc.SEMANTIC_POSITION, + aUv0: pc.SEMANTIC_TEXCOORD0 + } +}); + +// ==================== SCENE SETUP ==================== + +// Create camera entity +const camera = new pc.Entity('camera'); +camera.addComponent('camera', { + clearColor: new pc.Color(0.1, 0.1, 0.1), + projection: pc.PROJECTION_ORTHOGRAPHIC, + orthoHeight: 1 +}); +camera.setPosition(0, 0, 1); +app.root.addChild(camera); + +// Create unsorted visualization plane (top half) +const unsortedPlane = new pc.Entity('unsortedPlane'); +unsortedPlane.addComponent('render', { + type: 'plane', + material: unsortedMaterial, + castShadows: false, + receiveShadows: false +}); +unsortedPlane.setLocalPosition(0, 0.5, 0); +unsortedPlane.setLocalScale(2, 1, 1); +unsortedPlane.setEulerAngles(90, 0, 0); +app.root.addChild(unsortedPlane); + +// Create sorted visualization plane (bottom half) +const sortedPlane = new pc.Entity('sortedPlane'); +sortedPlane.addComponent('render', { + type: 'plane', + material: sortedMaterial, + castShadows: false, + receiveShadows: false +}); +sortedPlane.setLocalPosition(0, -0.5, 0); +sortedPlane.setLocalScale(2, 1, 1); +sortedPlane.setEulerAngles(90, 0, 0); +app.root.addChild(sortedPlane); + +// Create spinning cube for visual frame rate indicator +const cube = new pc.Entity('cube'); +cube.addComponent('render', { + type: 'box' +}); +cube.setLocalPosition(0, 0, 0.3); +cube.setLocalScale(0.15, 0.15, 0.15); +app.root.addChild(cube); + +// Create directional light for the cube +const light = new pc.Entity('light'); +light.addComponent('light'); +light.setEulerAngles(45, 30, 0); +app.root.addChild(light); + +// ==================== HELPER FUNCTIONS ==================== + +/** + * Calculates the optimal texture size for storing N elements. + * + * @param {number} numElements - Number of elements. + * @returns {{width: number, height: number}} Texture dimensions. + */ +function calcTextureSize(numElements) { + const pixels = Math.ceil(numElements); + const size = Math.ceil(Math.sqrt(pixels)); + return { width: size, height: size }; +} + +/** + * Recreates the keys texture and generates random data. + */ +function regenerateData() { + const numElements = currentNumElements; + const numBits = currentNumBits; + const maxValue = (1 << numBits) - 1; + + // Calculate non-POT texture size + const { width, height } = calcTextureSize(numElements); + + // Destroy old texture + if (keysTexture) { + keysTexture.destroy(); + } + + // Create new source keys texture + keysTexture = new pc.Texture(device, { + name: 'SourceKeys', + width: width, + height: height, + format: pc.PIXELFORMAT_R32U, + mipmaps: false, + minFilter: pc.FILTER_NEAREST, + magFilter: pc.FILTER_NEAREST, + addressU: pc.ADDRESS_CLAMP_TO_EDGE, + addressV: pc.ADDRESS_CLAMP_TO_EDGE + }); + + // Generate random test data directly into texture (linear layout) + const texData = keysTexture.lock(); + + // also keep original values for verification + originalValues = []; + + for (let i = 0; i < numElements; i++) { + const value = Math.floor(Math.random() * maxValue); + texData[i] = value; + originalValues.push(value); + } + + // Fill remaining pixels with max value + 1 (will sort to end) + for (let i = numElements; i < texData.length; i++) { + texData[i] = maxValue + 1; + } + + keysTexture.unlock(); + + needsRegen = false; +} + +/** + * Runs the GPU sort. + * + * @param {boolean} [verify] - Whether to verify results. + */ +function runSort(verify = false) { + if (!keysTexture || !radixSort) return; + + // Execute the GPU sort and get the sorted indices texture + const sortedIndices = radixSort.sort(keysTexture, currentNumElements, currentNumBits); + + // Update materials with the sorted texture + updateMaterialParameters(sortedIndices); + + // Verify results if requested + if (verify) { + verifyResults(sortedIndices); + } +} + +/** + * Updates material parameters after sort completes or data changes. + * + * @param {pc.Texture} sortedIndices - The sorted indices texture. + */ +function updateMaterialParameters(sortedIndices) { + if (!keysTexture || !sortedIndices) { + return; + } + + // Update unsorted material + unsortedMaterial.setParameter('keysTexture', keysTexture); + unsortedMaterial.setParameter('maxValue', (1 << currentNumBits) - 1); + unsortedMaterial.setParameter('elementCount', currentNumElements); + unsortedMaterial.setParameter('textureSize', [keysTexture.width, keysTexture.height]); + unsortedMaterial.setParameter('debugMode', 0.0); + unsortedMaterial.update(); + + // Update sorted material + sortedMaterial.setParameter('sortedIndices', sortedIndices); + sortedMaterial.setParameter('keysTexture', keysTexture); + sortedMaterial.setParameter('elementCount', currentNumElements); + sortedMaterial.setParameter('textureWidth', sortedIndices.width); + sortedMaterial.setParameter('maxValue', (1 << currentNumBits) - 1); + sortedMaterial.setParameter('sourceTextureSize', [keysTexture.width, keysTexture.height]); + sortedMaterial.setParameter('debugMode', 0.0); + sortedMaterial.update(); +} + +/** + * Downloads and verifies the sorted results. Only logs if validation fails. + * + * @param {pc.Texture} sortedIndices - The sorted indices texture to verify. + */ +async function verifyResults(sortedIndices) { + if (!sortedIndices) { + console.error('No sorted indices texture available'); + return; + } + + // Capture current state before async operation (use slice() for large arrays, not spread) + const capturedOriginalValues = originalValues.slice(); + const capturedNumElements = currentNumElements; + const width = sortedIndices.width; + + // Read the sorted indices texture (R32U) + const indicesResult = await sortedIndices.read(0, 0, width, width, { + immediate: true + }); + + // Extract sorted indices (stored in linear order) + const sortedIndicesArray = []; + for (let i = 0; i < capturedNumElements; i++) { + sortedIndicesArray.push(indicesResult[i]); + } + + // Get sorted values by looking up original values (using captured copy) + const sortedValues = sortedIndicesArray.map(idx => capturedOriginalValues[idx]); + + // Verify sorting - only log on failure + let errorCount = 0; + for (let i = 1; i < sortedValues.length; i++) { + if (sortedValues[i] < sortedValues[i - 1]) { + if (errorCount < 5) { + console.error(`Sort error at index ${i}: ${sortedValues[i - 1]} > ${sortedValues[i]}`); + } + errorCount++; + } + } + + if (errorCount > 0) { + console.error(`✗ [${device.deviceType}] Array is NOT correctly sorted (${errorCount} errors, ${(errorCount / capturedNumElements * 100).toFixed(2)}%)`); + } +} + +// Initialize control values in the data observer +data.set('options', { + elementsK: 1000, + bits: 16 +}); + +// Handle control changes from data binding +data.on('*:set', (/** @type {string} */ path, /** @type {any} */ value) => { + if (path === 'options.elementsK') { + const newElements = value * 1000; + if (newElements !== currentNumElements) { + currentNumElements = newElements; + needsRegen = true; + } + } else if (path === 'options.bits') { + if (value !== currentNumBits) { + currentNumBits = value; + needsRegen = true; + } + } +}); + +// Update loop - continuously sorts every frame +app.on('update', (/** @type {number} */ dt) => { + // Rotate the cube for visual frame rate indication + cube.rotate(10 * dt, 20 * dt, 30 * dt); + + // Regenerate data when parameters change + const verify = needsRegen; + if (needsRegen) { + regenerateData(); + } + + // Sort every frame, verify only after regeneration + runSort(verify); +}); + +export { app }; diff --git a/examples/src/examples/test/radix-sort.sorted.glsl.frag b/examples/src/examples/test/radix-sort.sorted.glsl.frag new file mode 100644 index 00000000000..03e72dffdb9 --- /dev/null +++ b/examples/src/examples/test/radix-sort.sorted.glsl.frag @@ -0,0 +1,48 @@ +precision highp float; +precision highp usampler2D; + +uniform usampler2D sortedIndices; +uniform usampler2D keysTexture; +uniform float elementCount; +uniform float textureWidth; +uniform float maxValue; +uniform vec2 sourceTextureSize; +uniform float debugMode; +varying vec2 vUv0; + +void main() { + vec2 uv = vUv0; + + // Debug mode: show UVs as colors + if (debugMode > 0.5) { + gl_FragColor = vec4(uv.x, uv.y, 0.5, 1.0); + return; + } + + // Calculate linear index from UV position + int pixelX = int(uv.x * textureWidth); + int pixelY = int(uv.y * textureWidth); + uint linearIdx = uint(pixelY) * uint(textureWidth) + uint(pixelX); + + if (float(linearIdx) >= elementCount) { + gl_FragColor = vec4(0.2, 0.2, 0.2, 1.0); + return; + } + + // Get the original index at this sorted position (linear layout) + uint tw = uint(textureWidth); + uint origIdx = texelFetch(sortedIndices, ivec2(linearIdx % tw, linearIdx / tw), 0).r; + + // Convert original index to source texture coordinates + int srcX = int(origIdx) % int(sourceTextureSize.x); + int srcY = int(origIdx) / int(sourceTextureSize.x); + + // Look up the key value from the source texture + float value = float(texelFetch(keysTexture, ivec2(srcX, srcY), 0).r); + float normalized = value / maxValue; + + // Use same color scheme as unsorted view: blue (low) to red (high) + vec3 color = mix(vec3(0.1, 0.2, 0.8), vec3(0.9, 0.3, 0.1), normalized); + gl_FragColor = vec4(color, 1.0); +} + diff --git a/examples/src/examples/test/radix-sort.sorted.wgsl.frag b/examples/src/examples/test/radix-sort.sorted.wgsl.frag new file mode 100644 index 00000000000..c34ab29de24 --- /dev/null +++ b/examples/src/examples/test/radix-sort.sorted.wgsl.frag @@ -0,0 +1,49 @@ +var sortedIndices: texture_2d; +var keysTexture: texture_2d; + +uniform elementCount: f32; +uniform textureWidth: f32; +uniform maxValue: f32; +uniform sourceTextureSize: vec2f; +uniform debugMode: f32; + +varying vUv0: vec2f; + +@fragment +fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + let uv = input.vUv0; + + // Debug mode: show UVs as colors + if (uniform.debugMode > 0.5) { + output.color = vec4f(uv.x, uv.y, 0.5, 1.0); + return output; + } + + let pixelX = i32(uv.x * uniform.textureWidth); + let pixelY = i32(uv.y * uniform.textureWidth); + let linearIdx = u32(pixelY) * u32(uniform.textureWidth) + u32(pixelX); + + if (f32(linearIdx) >= uniform.elementCount) { + output.color = vec4f(0.2, 0.2, 0.2, 1.0); + return output; + } + + // Get the original index at this sorted position (linear layout) + let tw = u32(uniform.textureWidth); + let origIdx = textureLoad(sortedIndices, vec2i(i32(linearIdx % tw), i32(linearIdx / tw)), 0).r; + + // Convert original index to source texture coordinates + let srcX = i32(origIdx) % i32(uniform.sourceTextureSize.x); + let srcY = i32(origIdx) / i32(uniform.sourceTextureSize.x); + + // Look up the key value from the source texture + let value = f32(textureLoad(keysTexture, vec2i(srcX, srcY), 0).r); + let normalized = value / uniform.maxValue; + + // Use same color scheme as unsorted view: blue (low) to red (high) + let color = mix(vec3f(0.1, 0.2, 0.8), vec3f(0.9, 0.3, 0.1), normalized); + output.color = vec4f(color, 1.0); + return output; +} + diff --git a/examples/src/examples/test/radix-sort.unsorted.glsl.frag b/examples/src/examples/test/radix-sort.unsorted.glsl.frag new file mode 100644 index 00000000000..853806e13ed --- /dev/null +++ b/examples/src/examples/test/radix-sort.unsorted.glsl.frag @@ -0,0 +1,36 @@ +precision highp float; +precision highp usampler2D; +uniform usampler2D keysTexture; +uniform float maxValue; +uniform float elementCount; +uniform vec2 textureSize; +uniform float debugMode; +varying vec2 vUv0; + +void main() { + vec2 uv = vUv0; + + // Debug mode: show UVs as colors + if (debugMode > 0.5) { + gl_FragColor = vec4(uv.x, uv.y, 0.0, 1.0); + return; + } + + // Scale UV to texture coordinates + int x = int(uv.x * textureSize.x); + int y = int(uv.y * textureSize.y); + int idx = y * int(textureSize.x) + x; + + if (float(idx) >= elementCount) { + gl_FragColor = vec4(0.2, 0.2, 0.2, 1.0); + return; + } + + float value = float(texelFetch(keysTexture, ivec2(x, y), 0).r); + float normalized = value / maxValue; + + // Color gradient based on value + vec3 color = mix(vec3(0.1, 0.2, 0.8), vec3(0.9, 0.3, 0.1), normalized); + gl_FragColor = vec4(color, 1.0); +} + diff --git a/examples/src/examples/test/radix-sort.unsorted.wgsl.frag b/examples/src/examples/test/radix-sort.unsorted.wgsl.frag new file mode 100644 index 00000000000..fbc8b67c356 --- /dev/null +++ b/examples/src/examples/test/radix-sort.unsorted.wgsl.frag @@ -0,0 +1,37 @@ +var keysTexture: texture_2d; + +uniform maxValue: f32; +uniform elementCount: f32; +uniform textureSize: vec2f; +uniform debugMode: f32; + +varying vUv0: vec2f; + +@fragment +fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + let uv = input.vUv0; + + // Debug mode: show UVs as colors + if (uniform.debugMode > 0.5) { + output.color = vec4f(uv.x, uv.y, 0.0, 1.0); + return output; + } + + let x = i32(uv.x * uniform.textureSize.x); + let y = i32(uv.y * uniform.textureSize.y); + let idx = y * i32(uniform.textureSize.x) + x; + + if (f32(idx) >= uniform.elementCount) { + output.color = vec4f(0.2, 0.2, 0.2, 1.0); + return output; + } + + let value = f32(textureLoad(keysTexture, vec2i(x, y), 0).r); + let normalized = value / uniform.maxValue; + + let color = mix(vec3f(0.1, 0.2, 0.8), vec3f(0.9, 0.3, 0.1), normalized); + output.color = vec4f(color, 1.0); + return output; +} + diff --git a/examples/src/examples/test/radix-sort.vert.glsl b/examples/src/examples/test/radix-sort.vert.glsl new file mode 100644 index 00000000000..ac9a4739d67 --- /dev/null +++ b/examples/src/examples/test/radix-sort.vert.glsl @@ -0,0 +1,13 @@ +attribute vec3 aPosition; +attribute vec2 aUv0; + +uniform mat4 matrix_model; +uniform mat4 matrix_viewProjection; + +varying vec2 vUv0; + +void main() { + vUv0 = aUv0; + gl_Position = matrix_viewProjection * matrix_model * vec4(aPosition, 1.0); +} + diff --git a/examples/src/examples/test/radix-sort.vert.wgsl b/examples/src/examples/test/radix-sort.vert.wgsl new file mode 100644 index 00000000000..4f105a1385b --- /dev/null +++ b/examples/src/examples/test/radix-sort.vert.wgsl @@ -0,0 +1,16 @@ +attribute aPosition: vec3f; +attribute aUv0: vec2f; + +uniform matrix_model: mat4x4f; +uniform matrix_viewProjection: mat4x4f; + +varying vUv0: vec2f; + +@vertex +fn vertexMain(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.vUv0 = input.aUv0; + output.position = uniform.matrix_viewProjection * uniform.matrix_model * vec4f(input.aPosition, 1.0); + return output; +} + diff --git a/examples/src/examples/test/xr-views.example.mjs b/examples/src/examples/test/xr-views.example.mjs index 650f57f08b4..b8e66596669 100644 --- a/examples/src/examples/test/xr-views.example.mjs +++ b/examples/src/examples/test/xr-views.example.mjs @@ -17,9 +17,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/button-basic.example.mjs b/examples/src/examples/user-interface/button-basic.example.mjs index 253fa66b259..5d569f8f30c 100644 --- a/examples/src/examples/user-interface/button-basic.example.mjs +++ b/examples/src/examples/user-interface/button-basic.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/button-sprite.example.mjs b/examples/src/examples/user-interface/button-sprite.example.mjs index 65a34ddc039..c2613cceac7 100644 --- a/examples/src/examples/user-interface/button-sprite.example.mjs +++ b/examples/src/examples/user-interface/button-sprite.example.mjs @@ -12,9 +12,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/custom-shader.example.mjs b/examples/src/examples/user-interface/custom-shader.example.mjs index ce18e0682c6..f8486667560 100644 --- a/examples/src/examples/user-interface/custom-shader.example.mjs +++ b/examples/src/examples/user-interface/custom-shader.example.mjs @@ -10,9 +10,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); @@ -72,8 +70,10 @@ assetListLoader.load(() => { // Create a new material with the new shader and additive alpha blending const material = new pc.ShaderMaterial({ uniqueName: 'myUIShader', - vertexGLSL: files['shader.vert'], - fragmentGLSL: files['shader.frag'], + vertexGLSL: files['shader.glsl.vert'], + fragmentGLSL: files['shader.glsl.frag'], + vertexWGSL: files['shader.wgsl.vert'], + fragmentWGSL: files['shader.wgsl.frag'], attributes: { vertex_position: pc.SEMANTIC_POSITION, vertex_texCoord0: pc.SEMANTIC_TEXCOORD0 diff --git a/examples/src/examples/user-interface/custom-shader.shader.frag b/examples/src/examples/user-interface/custom-shader.shader.glsl.frag similarity index 100% rename from examples/src/examples/user-interface/custom-shader.shader.frag rename to examples/src/examples/user-interface/custom-shader.shader.glsl.frag diff --git a/examples/src/examples/user-interface/custom-shader.shader.vert b/examples/src/examples/user-interface/custom-shader.shader.glsl.vert similarity index 100% rename from examples/src/examples/user-interface/custom-shader.shader.vert rename to examples/src/examples/user-interface/custom-shader.shader.glsl.vert diff --git a/examples/src/examples/user-interface/custom-shader.shader.wgsl.frag b/examples/src/examples/user-interface/custom-shader.shader.wgsl.frag new file mode 100644 index 00000000000..2c6ef6e4418 --- /dev/null +++ b/examples/src/examples/user-interface/custom-shader.shader.wgsl.frag @@ -0,0 +1,35 @@ + +/** + * Simple Color-Inverse Fragment Shader with intensity control. + * + * Usage: the following parameters must be set: + * uDiffuseMap: image texture. + * amount: float that controls the amount of the inverse-color effect. 0 means none (normal color), while 1 means full inverse. + * + * Additionally, the Vertex shader that is paired with this Fragment shader must specify: + * varying vec2 vUv0: for the UV. + */ + +#include "gammaPS" + +// Additional varying from vertex shader +varying vUv0: vec2f; + +// Custom Parameters (must be set from code via material.setParameter()) +var uDiffuseMap: texture_2d; +var uDiffuseMapSampler: sampler; +uniform amount: f32; + +@fragment +fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + + let color: vec4f = textureSample(uDiffuseMap, uDiffuseMapSampler, input.vUv0); + let roloc: vec3f = vec3f(1.0) - color.rgb; + let mixedColor: vec3f = mix(color.rgb, roloc, uniform.amount); + let correctedColor: vec3f = gammaCorrectOutput(mixedColor); + + output.color = vec4f(correctedColor, color.a); + return output; +} + diff --git a/examples/src/examples/user-interface/custom-shader.shader.wgsl.vert b/examples/src/examples/user-interface/custom-shader.shader.wgsl.vert new file mode 100644 index 00000000000..1fe1efa5fdc --- /dev/null +++ b/examples/src/examples/user-interface/custom-shader.shader.wgsl.vert @@ -0,0 +1,35 @@ + +/** + * Simple Screen-Space Vertex Shader with one UV coordinate. + * This shader is useful for simple UI shaders. + * + * Usage: the following attributes must be configured when creating a new pc.Shader: + * vertex_position: pc.SEMANTIC_POSITION + * vertex_texCoord0: pc.SEMANTIC_TEXCOORD0 + */ + +// Default PlayCanvas uniforms +uniform matrix_viewProjection: mat4x4f; +uniform matrix_model: mat4x4f; + +// Additional inputs +attribute vertex_position: vec3f; +attribute vertex_texCoord0: vec2f; + +// Additional shader outputs +varying vUv0: vec2f; + +@vertex +fn vertexMain(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + // UV is simply passed along as varying + output.vUv0 = input.vertex_texCoord0; + + // Position for screen-space + var pos: vec4f = uniform.matrix_model * vec4f(input.vertex_position, 1.0); + output.position = vec4f(pos.xy, 0.0, 1.0); + + return output; +} + diff --git a/examples/src/examples/user-interface/layout-group.example.mjs b/examples/src/examples/user-interface/layout-group.example.mjs index 448ac9da5b6..7411f46671c 100644 --- a/examples/src/examples/user-interface/layout-group.example.mjs +++ b/examples/src/examples/user-interface/layout-group.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/panel.example.mjs b/examples/src/examples/user-interface/panel.example.mjs index c226bbbd0bd..5d457a405cf 100644 --- a/examples/src/examples/user-interface/panel.example.mjs +++ b/examples/src/examples/user-interface/panel.example.mjs @@ -13,9 +13,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/particle-system.example.mjs b/examples/src/examples/user-interface/particle-system.example.mjs index 363137abdbc..67c1579cd62 100644 --- a/examples/src/examples/user-interface/particle-system.example.mjs +++ b/examples/src/examples/user-interface/particle-system.example.mjs @@ -10,9 +10,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/scroll-view.example.mjs b/examples/src/examples/user-interface/scroll-view.example.mjs index 7f6cea7ffa9..52834cc3037 100644 --- a/examples/src/examples/user-interface/scroll-view.example.mjs +++ b/examples/src/examples/user-interface/scroll-view.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/text-auto-font-size.example.mjs b/examples/src/examples/user-interface/text-auto-font-size.example.mjs index b48181d6701..0253aa6001f 100644 --- a/examples/src/examples/user-interface/text-auto-font-size.example.mjs +++ b/examples/src/examples/user-interface/text-auto-font-size.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/text-emojis.example.mjs b/examples/src/examples/user-interface/text-emojis.example.mjs index 60d2d77788c..30b6ae59687 100644 --- a/examples/src/examples/user-interface/text-emojis.example.mjs +++ b/examples/src/examples/user-interface/text-emojis.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/text-localization.example.mjs b/examples/src/examples/user-interface/text-localization.example.mjs index 4dc09131515..27e8b42400b 100644 --- a/examples/src/examples/user-interface/text-localization.example.mjs +++ b/examples/src/examples/user-interface/text-localization.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/text-typewriter.example.mjs b/examples/src/examples/user-interface/text-typewriter.example.mjs index f43738832b0..d4657d80da5 100644 --- a/examples/src/examples/user-interface/text-typewriter.example.mjs +++ b/examples/src/examples/user-interface/text-typewriter.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/text.example.mjs b/examples/src/examples/user-interface/text.example.mjs index 584d676e6b3..872571b5b65 100644 --- a/examples/src/examples/user-interface/text.example.mjs +++ b/examples/src/examples/user-interface/text.example.mjs @@ -9,9 +9,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/world-to-screen.example.mjs b/examples/src/examples/user-interface/world-to-screen.example.mjs index 508e189bf8f..637d2ed1e57 100644 --- a/examples/src/examples/user-interface/world-to-screen.example.mjs +++ b/examples/src/examples/user-interface/world-to-screen.example.mjs @@ -10,9 +10,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/user-interface/world-ui.example.mjs b/examples/src/examples/user-interface/world-ui.example.mjs index 80c08b0e8d4..c3566d7ecbb 100644 --- a/examples/src/examples/user-interface/world-ui.example.mjs +++ b/examples/src/examples/user-interface/world-ui.example.mjs @@ -11,9 +11,7 @@ const assets = { }; const gfxOptions = { - deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` + deviceTypes: [deviceType] }; const device = await pc.createGraphicsDevice(canvas, gfxOptions); diff --git a/examples/src/examples/xr/xr-ui.example.mjs b/examples/src/examples/xr/xr-ui.example.mjs index 5fdb728170a..756e21622a3 100644 --- a/examples/src/examples/xr/xr-ui.example.mjs +++ b/examples/src/examples/xr/xr-ui.example.mjs @@ -32,8 +32,6 @@ assets.font.id = 42; const gfxOptions = { deviceTypes: [deviceType], - glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, - twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js`, alpha: true }; diff --git a/examples/thumbnails/compute_edge-detect_large.webp b/examples/thumbnails/compute_edge-detect_large.webp new file mode 100644 index 00000000000..74e8b0984c1 Binary files /dev/null and b/examples/thumbnails/compute_edge-detect_large.webp differ diff --git a/examples/thumbnails/compute_edge-detect_small.webp b/examples/thumbnails/compute_edge-detect_small.webp new file mode 100644 index 00000000000..56723cc1b59 Binary files /dev/null and b/examples/thumbnails/compute_edge-detect_small.webp differ diff --git a/examples/thumbnails/gaussian-splatting_crop_large.webp b/examples/thumbnails/gaussian-splatting_crop_large.webp new file mode 100644 index 00000000000..f08117d128a Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_crop_large.webp differ diff --git a/examples/thumbnails/gaussian-splatting_crop_small.webp b/examples/thumbnails/gaussian-splatting_crop_small.webp new file mode 100644 index 00000000000..17d99741849 Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_crop_small.webp differ diff --git a/examples/thumbnails/gaussian-splatting_flipbook_large.webp b/examples/thumbnails/gaussian-splatting_flipbook_large.webp new file mode 100644 index 00000000000..14b3d6555cf Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_flipbook_large.webp differ diff --git a/examples/thumbnails/gaussian-splatting_flipbook_small.webp b/examples/thumbnails/gaussian-splatting_flipbook_small.webp new file mode 100644 index 00000000000..43bb69a010f Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_flipbook_small.webp differ diff --git a/examples/thumbnails/gaussian-splatting_shadows_large.webp b/examples/thumbnails/gaussian-splatting_shadows_large.webp new file mode 100644 index 00000000000..ffd07d3cd05 Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_shadows_large.webp differ diff --git a/examples/thumbnails/gaussian-splatting_shadows_small.webp b/examples/thumbnails/gaussian-splatting_shadows_small.webp new file mode 100644 index 00000000000..38f1bbea799 Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_shadows_small.webp differ diff --git a/examples/thumbnails/gaussian-splatting_viewer_large.webp b/examples/thumbnails/gaussian-splatting_viewer_large.webp new file mode 100644 index 00000000000..aa9da136a00 Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_viewer_large.webp differ diff --git a/examples/thumbnails/gaussian-splatting_viewer_small.webp b/examples/thumbnails/gaussian-splatting_viewer_small.webp new file mode 100644 index 00000000000..bccadfec725 Binary files /dev/null and b/examples/thumbnails/gaussian-splatting_viewer_small.webp differ diff --git a/examples/thumbnails/misc_annotations_large.webp b/examples/thumbnails/misc_annotations_large.webp new file mode 100644 index 00000000000..bb113278da9 Binary files /dev/null and b/examples/thumbnails/misc_annotations_large.webp differ diff --git a/examples/thumbnails/misc_annotations_small.webp b/examples/thumbnails/misc_annotations_small.webp new file mode 100644 index 00000000000..3993868d73d Binary files /dev/null and b/examples/thumbnails/misc_annotations_small.webp differ diff --git a/examples/thumbnails/test_attenuation_large.webp b/examples/thumbnails/test_attenuation_large.webp new file mode 100644 index 00000000000..1eb694fd238 Binary files /dev/null and b/examples/thumbnails/test_attenuation_large.webp differ diff --git a/examples/thumbnails/test_attenuation_small.webp b/examples/thumbnails/test_attenuation_small.webp new file mode 100644 index 00000000000..fd988f73f5a Binary files /dev/null and b/examples/thumbnails/test_attenuation_small.webp differ diff --git a/examples/thumbnails/test_radix-sort_large.webp b/examples/thumbnails/test_radix-sort_large.webp new file mode 100644 index 00000000000..116e3b83389 Binary files /dev/null and b/examples/thumbnails/test_radix-sort_large.webp differ diff --git a/examples/thumbnails/test_radix-sort_small.webp b/examples/thumbnails/test_radix-sort_small.webp new file mode 100644 index 00000000000..a94879d0092 Binary files /dev/null and b/examples/thumbnails/test_radix-sort_small.webp differ diff --git a/package-lock.json b/package-lock.json index 116dcb92553..bdf067d820a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,56 +1,64 @@ { "name": "playcanvas", - "version": "2.14.0-beta.0", + "version": "2.15.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "playcanvas", - "version": "2.14.0-beta.0", + "version": "2.15.0-beta.0", "license": "MIT", "dependencies": { "@types/webxr": "^0.5.24", - "@webgpu/types": "^0.1.65" + "@webgpu/types": "^0.1.66" }, "devDependencies": { "@playcanvas/eslint-config": "2.1.0", - "@rollup/plugin-node-resolve": "16.0.2", + "@rollup/plugin-node-resolve": "16.0.3", "@rollup/plugin-strip": "3.0.4", "@rollup/plugin-swc": "0.4.0", "@rollup/plugin-terser": "0.4.4", "@rollup/pluginutils": "5.3.0", - "@swc/core": "1.13.5", - "@types/node": "24.7.0", + "@swc/core": "1.15.3", + "@types/node": "24.10.1", "c8": "10.1.3", - "chai": "6.2.0", - "eslint": "9.37.0", + "chai": "6.2.1", + "eslint": "9.39.1", "fflate": "0.8.2", - "globals": "16.4.0", - "jsdom": "27.0.0", - "mocha": "11.7.4", - "publint": "0.3.14", - "rollup": "4.52.4", - "rollup-plugin-dts": "6.2.3", + "globals": "16.5.0", + "jsdom": "27.2.0", + "mocha": "11.7.5", + "nise": "6.1.1", + "publint": "0.3.15", + "rollup": "4.53.3", + "rollup-plugin-dts": "6.3.0", "rollup-plugin-jscc": "2.0.0", - "rollup-plugin-visualizer": "6.0.4", + "rollup-plugin-visualizer": "6.0.5", "serve": "14.2.5", - "sinon": "19.0.5", - "typedoc": "0.28.13", - "typedoc-plugin-mdn-links": "5.0.9", - "typedoc-plugin-missing-exports": "4.1.0", + "sinon": "21.0.0", + "typedoc": "0.28.15", + "typedoc-plugin-mdn-links": "5.0.10", + "typedoc-plugin-missing-exports": "4.1.2", "typescript": "5.9.3" }, "engines": { "node": ">=18.0.0" }, "optionalDependencies": { - "canvas": "3.1.0" + "canvas": "3.2.0" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.24", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.24.tgz", + "integrity": "sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==", + "dev": true, + "license": "MIT" + }, "node_modules/@asamuzakjp/css-color": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", - "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", + "integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==", "dev": true, "license": "MIT", "dependencies": { @@ -58,23 +66,13 @@ "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.1" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" + "lru-cache": "^11.2.2" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.6.1.tgz", - "integrity": "sha512-8QT9pokVe1fUt1C8IrJketaeFOdRfTOS96DL3EBjE8CRZm3eHnwMlQe2NPoOSEYPwJ5Q25uYoX1+m9044l3ysQ==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", + "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", "dev": true, "license": "MIT", "dependencies": { @@ -85,16 +83,6 @@ "lru-cache": "^11.2.2" } }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@asamuzakjp/nwsapi": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", @@ -119,9 +107,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "optional": true, @@ -235,9 +223,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", - "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.20.tgz", + "integrity": "sha512-8BHsjXfSciZxjmHQOuVdW2b8WLUPts9a+mfL13/PzEviufUEW2xnvQuOlKs9dRBHgRqJ53SF/DUoK9+MZk72oQ==", "dev": true, "funding": [ { @@ -252,9 +240,6 @@ "license": "MIT-0", "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, "node_modules/@csstools/css-tokenizer": { @@ -278,17 +263,20 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", - "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", + "version": "0.50.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", + "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==", "dev": true, + "license": "MIT", "dependencies": { + "@types/estree": "^1.0.6", + "@typescript-eslint/types": "^8.11.0", "comment-parser": "1.4.1", "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -324,9 +312,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -334,13 +322,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -349,22 +337,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", - "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.16.0" + "@eslint/core": "^0.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -375,10 +363,11 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -386,7 +375,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -402,6 +391,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -410,9 +400,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", - "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { @@ -423,9 +413,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -433,13 +423,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.16.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -447,16 +437,16 @@ } }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", - "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.17.0.tgz", + "integrity": "sha512-Bpf6WuFar20ZXL6qU6VpVl4bVQfyyYiX+6O4xrns4nkU3Mr8paeupDbS1HENpcLOYj7pN4Rkd/yCaPA0vQwKww==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.13.0", - "@shikijs/langs": "^3.13.0", - "@shikijs/themes": "^3.13.0", - "@shikijs/types": "^3.13.0", + "@shikijs/engine-oniguruma": "^3.17.0", + "@shikijs/langs": "^3.17.0", + "@shikijs/themes": "^3.17.0", + "@shikijs/types": "^3.17.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -471,33 +461,19 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -513,9 +489,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -555,18 +531,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -579,20 +551,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { @@ -601,16 +563,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -654,6 +616,7 @@ "resolved": "https://registry.npmjs.org/@playcanvas/eslint-config/-/eslint-config-2.1.0.tgz", "integrity": "sha512-m8IMRsdmxeSGvmfcl+wYk+dTF7I4wCbB+FozT//yMCUVCj7lHeaWWeHdouRUMgP5FWzEZ6q6u6bjXzOgUlbBeg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsdoc": "^50.6.11", @@ -677,9 +640,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.2.tgz", - "integrity": "sha512-tCtHJ2BlhSoK4cCs25NMXfV7EALKr0jyasmqVCq3y9cBrKdmJhtsy1iTz36Xhk/O+pDJbzawxF4K6ZblqCnITQ==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", "dev": true, "license": "MIT", "dependencies": { @@ -794,9 +757,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -808,9 +771,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -822,9 +785,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -836,9 +799,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -850,9 +813,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -864,9 +827,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -878,9 +841,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -892,9 +855,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -906,9 +869,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -920,9 +883,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -934,9 +897,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -948,9 +911,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -962,9 +925,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", "cpu": [ "riscv64" ], @@ -976,9 +939,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -990,9 +953,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -1004,9 +967,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -1018,9 +981,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -1032,9 +995,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", "cpu": [ "arm64" ], @@ -1046,9 +1009,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -1060,9 +1023,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -1074,9 +1037,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", "cpu": [ "x64" ], @@ -1088,9 +1051,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", "cpu": [ "x64" ], @@ -1109,40 +1072,40 @@ "license": "MIT" }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", - "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.17.0.tgz", + "integrity": "sha512-flSbHZAiOZDNTrEbULY8DLWavu/TyVu/E7RChpLB4WvKX4iHMfj80C6Hi3TjIWaQtHOW0KC6kzMcuB5TO1hZ8Q==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.17.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", - "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.17.0.tgz", + "integrity": "sha512-icmur2n5Ojb+HAiQu6NEcIIJ8oWDFGGEpiqSCe43539Sabpx7Y829WR3QuUW2zjTM4l6V8Sazgb3rrHO2orEAw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.17.0" } }, "node_modules/@shikijs/themes": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", - "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.17.0.tgz", + "integrity": "sha512-/xEizMHLBmMHwtx4JuOkRf3zwhWD2bmG5BRr0IPjpcWpaq4C3mYEuTk/USAEglN0qPrTwEHwKVpSu/y2jhferA==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.17.0" } }, "node_modules/@shikijs/types": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", - "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.17.0.tgz", + "integrity": "sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==", "dev": true, "license": "MIT", "dependencies": { @@ -1178,14 +1141,13 @@ } }, "node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", "type-detect": "^4.1.0" } }, @@ -1207,15 +1169,15 @@ "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@swc/core": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", - "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz", + "integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.24" + "@swc/types": "^0.1.25" }, "engines": { "node": ">=10" @@ -1225,16 +1187,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.13.5", - "@swc/core-darwin-x64": "1.13.5", - "@swc/core-linux-arm-gnueabihf": "1.13.5", - "@swc/core-linux-arm64-gnu": "1.13.5", - "@swc/core-linux-arm64-musl": "1.13.5", - "@swc/core-linux-x64-gnu": "1.13.5", - "@swc/core-linux-x64-musl": "1.13.5", - "@swc/core-win32-arm64-msvc": "1.13.5", - "@swc/core-win32-ia32-msvc": "1.13.5", - "@swc/core-win32-x64-msvc": "1.13.5" + "@swc/core-darwin-arm64": "1.15.3", + "@swc/core-darwin-x64": "1.15.3", + "@swc/core-linux-arm-gnueabihf": "1.15.3", + "@swc/core-linux-arm64-gnu": "1.15.3", + "@swc/core-linux-arm64-musl": "1.15.3", + "@swc/core-linux-x64-gnu": "1.15.3", + "@swc/core-linux-x64-musl": "1.15.3", + "@swc/core-win32-arm64-msvc": "1.15.3", + "@swc/core-win32-ia32-msvc": "1.15.3", + "@swc/core-win32-x64-msvc": "1.15.3" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -1246,9 +1208,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", - "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.3.tgz", + "integrity": "sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==", "cpu": [ "arm64" ], @@ -1263,9 +1225,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", - "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz", + "integrity": "sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==", "cpu": [ "x64" ], @@ -1280,9 +1242,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", - "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz", + "integrity": "sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==", "cpu": [ "arm" ], @@ -1297,9 +1259,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", - "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz", + "integrity": "sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==", "cpu": [ "arm64" ], @@ -1314,9 +1276,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", - "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz", + "integrity": "sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==", "cpu": [ "arm64" ], @@ -1331,9 +1293,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", - "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz", + "integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==", "cpu": [ "x64" ], @@ -1348,9 +1310,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", - "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz", + "integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==", "cpu": [ "x64" ], @@ -1365,9 +1327,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", - "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz", + "integrity": "sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==", "cpu": [ "arm64" ], @@ -1382,9 +1344,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", - "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz", + "integrity": "sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==", "cpu": [ "ia32" ], @@ -1399,9 +1361,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", - "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz", + "integrity": "sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==", "cpu": [ "x64" ], @@ -1471,13 +1433,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.14.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/resolve": { @@ -1500,10 +1462,24 @@ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", "license": "MIT" }, + "node_modules/@typescript-eslint/types": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@webgpu/types": { - "version": "0.1.65", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.65.tgz", - "integrity": "sha512-cYrHab4d6wuVvDW5tdsfI6/o6vcLMDe6w2Citd1oS51Xxu2ycLCnVo4fqwujfKWijrZMInTJIKcXxteoy21nVA==", + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", + "integrity": "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==", "license": "BSD-3-Clause" }, "node_modules/@zeit/schemas": { @@ -1531,14 +1507,15 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -1550,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1617,9 +1595,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -1671,6 +1649,7 @@ "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" } @@ -1707,18 +1686,20 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1728,18 +1709,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1908,9 +1890,9 @@ } }, "node_modules/boxen/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { @@ -1921,9 +1903,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1971,9 +1953,9 @@ "license": "MIT" }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { @@ -2048,14 +2030,14 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -2069,6 +2051,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2087,24 +2070,24 @@ } }, "node_modules/canvas": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.1.0.tgz", - "integrity": "sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz", + "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==", "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1" + "prebuild-install": "^7.1.3" }, "engines": { "node": "^18.12.0 || >= 20.9.0" } }, "node_modules/chai": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", - "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", "dev": true, "license": "MIT", "engines": { @@ -2345,16 +2328,6 @@ "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2426,9 +2399,9 @@ } }, "node_modules/cssstyle": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", - "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz", + "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==", "dev": true, "license": "MIT", "dependencies": { @@ -2509,9 +2482,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2540,12 +2513,28 @@ } }, "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2620,9 +2609,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2682,9 +2671,9 @@ "license": "MIT" }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "optional": true, "dependencies": { @@ -2692,9 +2681,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -2705,9 +2694,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2715,18 +2704,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -2738,21 +2727,24 @@ "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", + "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -2761,7 +2753,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -2874,25 +2866,24 @@ } }, "node_modules/eslint": { - "version": "9.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", - "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.4.0", - "@eslint/core": "^0.16.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.37.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -2957,9 +2948,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, "license": "MIT", "dependencies": { @@ -2985,30 +2976,30 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -3029,20 +3020,21 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.6.11", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.11.tgz", - "integrity": "sha512-k4+MnBCGR8cuIB5MZ++FGd4gbXxjob2rX1Nq0q3nWFF4xSGZENTgTLZSjb+u9B8SAnP6lpGV2FJrBjllV3pVSg==", + "version": "50.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.8.0.tgz", + "integrity": "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.49.0", + "@es-joy/jsdoccomment": "~0.50.2", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.6", + "debug": "^4.4.1", "escape-string-regexp": "^4.0.0", - "espree": "^10.1.0", + "espree": "^10.3.0", "esquery": "^1.6.0", "parse-imports-exports": "^0.2.4", - "semver": "^7.6.3", + "semver": "^7.7.2", "spdx-expression-parse": "^4.0.0" }, "engines": { @@ -3053,10 +3045,11 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3065,9 +3058,9 @@ } }, "node_modules/eslint-plugin-regexp": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.7.0.tgz", - "integrity": "sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.10.0.tgz", + "integrity": "sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==", "dev": true, "license": "MIT", "dependencies": { @@ -3239,7 +3232,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -3333,13 +3327,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -3412,6 +3406,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3423,18 +3427,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -3500,9 +3504,9 @@ "optional": true }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -3534,9 +3538,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3560,9 +3564,9 @@ } }, "node_modules/globals": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { @@ -3803,6 +3807,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3812,6 +3817,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4050,14 +4056,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -4101,6 +4108,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", @@ -4382,9 +4402,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4420,9 +4440,9 @@ "optional": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -4470,22 +4490,22 @@ } }, "node_modules/jsdom": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", - "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.2.0.tgz", + "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/dom-selector": "^6.5.4", - "cssstyle": "^5.3.0", + "@acemir/cssom": "^0.9.23", + "@asamuzakjp/dom-selector": "^6.7.4", + "cssstyle": "^5.3.3", "data-urls": "^6.0.0", - "decimal.js": "^10.5.0", + "decimal.js": "^10.6.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "parse5": "^7.3.0", - "rrweb-cssom": "^0.8.0", + "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", @@ -4493,12 +4513,12 @@ "webidl-conversions": "^8.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0", - "ws": "^8.18.2", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -4520,7 +4540,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -4599,14 +4620,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4632,11 +4645,14 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/lunr": { "version": "2.3.9", @@ -4646,13 +4662,13 @@ "license": "MIT" }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-dir": { @@ -4672,9 +4688,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -4702,6 +4718,19 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4743,6 +4772,29 @@ "node": ">= 0.6" } }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4753,6 +4805,19 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4794,9 +4859,9 @@ "optional": true }, "node_modules/mocha": { - "version": "11.7.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz", - "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==", + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, "license": "MIT", "dependencies": { @@ -4889,26 +4954,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -4947,21 +4992,10 @@ "path-to-regexp": "^8.1.0" } }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/node-abi": { - "version": "3.74.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", - "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", "license": "MIT", "optional": true, "dependencies": { @@ -4972,9 +5006,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "optional": true, "bin": { @@ -5231,9 +5265,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.4.0.tgz", - "integrity": "sha512-rRZ+pR1Usc+ND9M2NkmCvE/LYJS+8ORVV9X0KuNSY/gFsp7RBHJM/ADh9LYq4Vvfq6QkKrW6/weuh8SMEtN5gw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "dev": true, "license": "MIT" }, @@ -5242,6 +5276,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -5254,6 +5289,7 @@ "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", "dev": true, + "license": "MIT", "dependencies": { "parse-statements": "1.0.11" } @@ -5262,12 +5298,13 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "dev": true, "license": "MIT", "dependencies": { @@ -5277,19 +5314,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5341,12 +5365,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/perf-regexes": { "version": "1.0.1", @@ -5366,9 +5401,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -5388,36 +5423,6 @@ "node": ">= 0.4" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -5445,61 +5450,6 @@ "node": ">=10" } }, - "node_modules/prebuild-install/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prebuild-install/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prebuild-install/node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5511,9 +5461,9 @@ } }, "node_modules/publint": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.14.tgz", - "integrity": "sha512-14/VNBvWsrBeqWNDw8c/DK5ERcZBUwL1rnkVx18cQnF3zadr3GfoYtvD8mxi1dhkWpaPHp8kfi92MDbjMeW3qw==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.15.tgz", + "integrity": "sha512-xPbRAPW+vqdiaKy5sVVY0uFAu3LaviaPO3pZ9FaRx59l9+U/RKR1OEbLhkug87cwiVKxPXyB4txsv5cad67u+A==", "dev": true, "license": "MIT", "dependencies": { @@ -5533,9 +5483,9 @@ } }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", "optional": true, "dependencies": { @@ -5754,13 +5704,13 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5779,14 +5729,15 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", "dependencies": { @@ -5800,39 +5751,39 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-dts": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.3.tgz", - "integrity": "sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.3.0.tgz", + "integrity": "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "engines": { "node": ">=16" @@ -5867,9 +5818,9 @@ } }, "node_modules/rollup-plugin-visualizer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.4.tgz", - "integrity": "sha512-q8Q7J/6YofkmaGW1sH/fPRAz37x/+pd7VBuaUU7lwvOS/YikuiiEU9jeb9PH8XHiq50XFrUsBbOxeAMYQ7KZkg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz", + "integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==", "dev": true, "license": "MIT", "dependencies": { @@ -5914,13 +5865,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -6107,28 +6051,22 @@ "range-parser": "1.2.0" } }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } + "license": "MIT" }, "node_modules/serve/node_modules/ajv": { "version": "8.12.0", @@ -6349,10 +6287,36 @@ "license": "MIT", "optional": true }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sinon": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.5.tgz", - "integrity": "sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6360,7 +6324,6 @@ "@sinonjs/fake-timers": "^13.0.5", "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", - "nise": "^6.1.1", "supports-color": "^7.2.0" }, "funding": { @@ -6386,13 +6349,13 @@ "license": "MIT" }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -6438,23 +6401,40 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/string_decoder": { "version": "1.3.0", @@ -6590,9 +6570,9 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -6696,9 +6676,9 @@ "license": "MIT" }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "optional": true, "dependencies": { @@ -6726,14 +6706,14 @@ } }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -6760,9 +6740,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6786,22 +6766,22 @@ } }, "node_modules/tldts": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", - "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.16" + "tldts-core": "^7.0.19" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", - "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", "dev": true, "license": "MIT" }, @@ -6972,13 +6952,13 @@ } }, "node_modules/typedoc": { - "version": "0.28.13", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", - "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@gerrit0/mini-shiki": "^3.12.0", + "@gerrit0/mini-shiki": "^3.17.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", @@ -6996,9 +6976,9 @@ } }, "node_modules/typedoc-plugin-mdn-links": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-5.0.9.tgz", - "integrity": "sha512-kXssRKBhUd0JeHzFmxWVsGWVFR9WXafe70Y8Ed+MYH2Nu2647cqfGQN1OBKgvXpmAT8MTpACmUIQ7GnQnh1/iw==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-5.0.10.tgz", + "integrity": "sha512-TOMj1+fyhqhdJaMwfkw7ANz+0KHjRVUnE/SorPW83wghElmsMxxCZhDSBgF2hRB9+qsf/qIjDw65RDay94E2Wg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -7006,9 +6986,9 @@ } }, "node_modules/typedoc-plugin-missing-exports": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-4.1.0.tgz", - "integrity": "sha512-p1M5jXnEbQ4qqy0erJz41BBZEDb8XDrbLjndlH4RhYEcymbdQr0xLF6yUw1GWBrhSIfkU98m3BELMAiQh+R1zA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-4.1.2.tgz", + "integrity": "sha512-WNoeWX9+8X3E3riuYPduilUTFefl1K+Z+5bmYqNeH5qcWjtnTRMbRzGdEQ4XXn1WEO4WCIlU0vf46Ca2y/mspg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -7016,9 +6996,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7082,9 +7062,9 @@ } }, "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -7285,16 +7265,17 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", - "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "for-each": "^0.3.3", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, @@ -7421,9 +7402,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 84fff070477..8c33e605906 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "playcanvas", - "version": "2.14.0-beta.0", + "version": "2.15.0-beta.0", "author": "PlayCanvas ", "homepage": "https://playcanvas.com", "description": "PlayCanvas WebGL game engine", @@ -79,38 +79,39 @@ ], "dependencies": { "@types/webxr": "^0.5.24", - "@webgpu/types": "^0.1.65" + "@webgpu/types": "^0.1.66" }, "devDependencies": { "@playcanvas/eslint-config": "2.1.0", - "@rollup/plugin-node-resolve": "16.0.2", + "@rollup/plugin-node-resolve": "16.0.3", "@rollup/plugin-strip": "3.0.4", "@rollup/plugin-swc": "0.4.0", "@rollup/plugin-terser": "0.4.4", "@rollup/pluginutils": "5.3.0", - "@swc/core": "1.13.5", - "@types/node": "24.7.0", + "@swc/core": "1.15.3", + "@types/node": "24.10.1", "c8": "10.1.3", - "chai": "6.2.0", - "eslint": "9.37.0", + "chai": "6.2.1", + "eslint": "9.39.1", "fflate": "0.8.2", - "globals": "16.4.0", - "jsdom": "27.0.0", - "mocha": "11.7.4", - "publint": "0.3.14", - "rollup": "4.52.4", - "rollup-plugin-dts": "6.2.3", + "globals": "16.5.0", + "jsdom": "27.2.0", + "mocha": "11.7.5", + "nise": "6.1.1", + "publint": "0.3.15", + "rollup": "4.53.3", + "rollup-plugin-dts": "6.3.0", "rollup-plugin-jscc": "2.0.0", - "rollup-plugin-visualizer": "6.0.4", + "rollup-plugin-visualizer": "6.0.5", "serve": "14.2.5", - "sinon": "19.0.5", - "typedoc": "0.28.13", - "typedoc-plugin-mdn-links": "5.0.9", - "typedoc-plugin-missing-exports": "4.1.0", + "sinon": "21.0.0", + "typedoc": "0.28.15", + "typedoc-plugin-mdn-links": "5.0.10", + "typedoc-plugin-missing-exports": "4.1.2", "typescript": "5.9.3" }, "optionalDependencies": { - "canvas": "3.1.0" + "canvas": "3.2.0" }, "scripts": { "build": "node build.mjs", diff --git a/scripts/esm/annotation.mjs b/scripts/esm/annotation.mjs new file mode 100644 index 00000000000..dfefbf8a8c0 --- /dev/null +++ b/scripts/esm/annotation.mjs @@ -0,0 +1,616 @@ +import { + CULLFACE_NONE, + FILTER_LINEAR, + PIXELFORMAT_RGBA8, + BlendState, + Color, + Entity, + Layer, + Mesh, + MeshInstance, + PlaneGeometry, + Script, + StandardMaterial, + Texture, + Vec3, + BLENDEQUATION_ADD, + BLENDMODE_ONE, + BLENDMODE_ONE_MINUS_SRC_ALPHA, + BLENDMODE_SRC_ALPHA +} from 'playcanvas'; + +/** @import { Application, CameraComponent, Quat } from 'playcanvas' */ + +// clamp the vertices of the hotspot so it is never clipped by the near or far plane +const depthClamp = ` + float f = gl_Position.z / gl_Position.w; + if (f > 1.0) { + gl_Position.z = gl_Position.w; + } else if (f < -1.0) { + gl_Position.z = -gl_Position.w; + } +`; + +const depthClampWGSL = ` + let f: f32 = output.position.z / output.position.w; + if (f > 1.0) { + output.position.z = output.position.w; + } else if (f < -1.0) { + output.position.z = -output.position.w; + } +`; + +const vec = new Vec3(); + +/** + * A script for creating interactive 3D annotations in a scene. Each annotation consists of: + * + * - A 3D hotspot that maintains constant screen-space size. The hotspot is rendered with muted + * appearance when obstructed by geometry but is still clickable. The hotspot relies on an + * invisible DOM element that matches the hotspot's size and position to detect clicks. + * - An annotation panel that shows title and description text. + */ +export class Annotation extends Script { + static scriptName = 'annotation'; + + /** @type {number} */ + static hotspotSize = 25; + + /** @type {Color} */ + static hotspotColor = new Color(0.8, 0.8, 0.8); + + /** @type {Color} */ + static hoverColor = new Color(1.0, 0.4, 0.0); + + /** @type {HTMLElement | null} */ + static parentDom = null; + + /** @type {HTMLStyleElement | null} */ + static styleSheet = null; + + /** @type {Entity | null} */ + static camera = null; + + /** @type {HTMLDivElement | null} */ + static tooltipDom = null; + + /** @type {HTMLDivElement | null} */ + static titleDom = null; + + /** @type {HTMLDivElement | null} */ + static textDom = null; + + /** @type {Layer[]} */ + static layers = []; + + /** @type {Mesh | null} */ + static mesh = null; + + /** @type {Annotation | null} */ + static activeAnnotation = null; + + /** @type {Annotation | null} */ + static hoverAnnotation = null; + + /** @type {number} */ + static opacity = 1.0; + + /** + * @type {string} + * @attribute + */ + label; + + /** + * @type {string} + * @attribute + */ + title; + + /** + * @type {string} + * @attribute + */ + text; + + /** + * @type {HTMLDivElement | null} + * @private + */ + hotspotDom = null; + + /** + * @type {Texture | null} + * @private + */ + texture = null; + + /** + * @type {StandardMaterial[]} + * @private + */ + materials = []; + + /** + * Injects required CSS styles into the document. + * @param {number} size - The size of the hotspot in screen pixels. + * @private + */ + static _injectStyles(size) { + const css = ` + .pc-annotation { + display: block; + position: absolute; + background-color: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px; + border-radius: 4px; + font-size: 14px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + pointer-events: none; + max-width: 200px; + word-wrap: break-word; + overflow-x: visible; + white-space: normal; + width: fit-content; + opacity: 0; + transition: opacity 0.2s ease-in-out; + visibility: hidden; + transform: translate(25px, -50%); + } + + .pc-annotation-title { + font-weight: bold; + margin-bottom: 4px; + } + + /* Add a little triangular arrow on the left edge of the tooltip */ + .pc-annotation::before { + content: ""; + position: absolute; + left: -8px; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-right: 8px solid rgba(0, 0, 0, 0.8); + } + + .pc-annotation-hotspot { + display: none; + position: absolute; + width: ${size + 5}px; + height: ${size + 5}px; + opacity: 0; + cursor: pointer; + transform: translate(-50%, -50%); + } + `; + + const style = document.createElement('style'); + style.textContent = css; + document.head.appendChild(style); + Annotation.styleSheet = style; + } + + /** + * Initialize static resources. + * @param {Application} app - The application instance + * @private + */ + static _initializeStatic(app) { + if (Annotation.styleSheet) { + return; + } + + Annotation._injectStyles(Annotation.hotspotSize); + + if (Annotation.parentDom === null) { + Annotation.parentDom = document.body; + } + + const { layers } = app.scene; + const worldLayer = layers.getLayerByName('World'); + + const createLayer = (name, semitrans) => { + const layer = new Layer({ name: name }); + const idx = semitrans ? layers.getTransparentIndex(worldLayer) : layers.getOpaqueIndex(worldLayer); + layers.insert(layer, idx + 1); + return layer; + }; + + Annotation.layers = [ + createLayer('HotspotBase', false), + createLayer('HotspotOverlay', true) + ]; + + if (Annotation.camera === null) { + Annotation.camera = app.root.findComponent('camera').entity; + } + + Annotation.camera.camera.layers = [ + ...Annotation.camera.camera.layers, + ...Annotation.layers.map(layer => layer.id) + ]; + + Annotation.mesh = Mesh.fromGeometry(app.graphicsDevice, new PlaneGeometry({ + widthSegments: 1, + lengthSegments: 1 + })); + + // Initialize tooltip dom + Annotation.tooltipDom = document.createElement('div'); + Annotation.tooltipDom.className = 'pc-annotation'; + + Annotation.titleDom = document.createElement('div'); + Annotation.titleDom.className = 'pc-annotation-title'; + Annotation.tooltipDom.appendChild(Annotation.titleDom); + + Annotation.textDom = document.createElement('div'); + Annotation.textDom.className = 'pc-annotation-text'; + Annotation.tooltipDom.appendChild(Annotation.textDom); + + Annotation.parentDom.appendChild(Annotation.tooltipDom); + } + + /** + * Creates a circular hotspot texture. + * @param {Application} app - The PlayCanvas application + * @param {string} label - Label text to draw on the hotspot + * @param {number} [size] - The texture size (should be power of 2) + * @param {number} [borderWidth] - The border width in pixels + * @returns {Texture} The hotspot texture + * @private + */ + static _createHotspotTexture(app, label, size = 64, borderWidth = 6) { + // Create canvas for hotspot texture + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + + // First clear with stroke color at zero alpha + ctx.fillStyle = 'white'; + ctx.globalAlpha = 0; + ctx.fillRect(0, 0, size, size); + ctx.globalAlpha = 1.0; + + // Draw dark circle with light border + const centerX = size / 2; + const centerY = size / 2; + const radius = (size / 2) - 4; // Leave space for border + + // Draw main circle + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); + ctx.fillStyle = 'black'; + ctx.fill(); + + // Draw border + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = 'white'; + ctx.stroke(); + + // Draw text + ctx.font = 'bold 32px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = 'white'; + ctx.fillText(label, Math.floor(canvas.width / 2), Math.floor(canvas.height / 2) + 1); + + // get pixel data + const imageData = ctx.getImageData(0, 0, size, size); + const data = imageData.data; + + // set the color channel of semitransparent pixels to white so the blending at + // the edges is correct + for (let i = 0; i < data.length; i += 4) { + const a = data[i + 3]; + if (a < 255) { + data[i] = 255; + data[i + 1] = 255; + data[i + 2] = 255; + } + } + + const texture = new Texture(app.graphicsDevice, { + width: size, + height: size, + format: PIXELFORMAT_RGBA8, + magFilter: FILTER_LINEAR, + minFilter: FILTER_LINEAR, + mipmaps: false, + levels: [new Uint8Array(data.buffer)] + }); + + return texture; + } + + /** + * Creates a material for hotspot rendering. + * @param {Texture} texture - The texture to use for emissive and opacity + * @param {object} [options] - Material options + * @param {number} [options.opacity] - Base opacity multiplier + * @param {boolean} [options.depthTest] - Whether to perform depth testing + * @param {boolean} [options.depthWrite] - Whether to write to depth buffer + * @returns {StandardMaterial} The configured material + * @private + */ + static _createHotspotMaterial(texture, { opacity = 1, depthTest = true, depthWrite = true } = {}) { + const material = new StandardMaterial(); + + // Base properties + material.diffuse = Color.BLACK; + material.emissive.copy(Annotation.hotspotColor); + material.emissiveMap = texture; + material.opacityMap = texture; + + // Alpha properties + material.opacity = opacity; + material.alphaTest = 0.01; + material.blendState = new BlendState( + true, + BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, + BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE + ); + + // Depth properties + material.depthTest = depthTest; + material.depthWrite = depthWrite; + + // Rendering properties + material.cull = CULLFACE_NONE; + material.useLighting = false; + + material.shaderChunks.glsl.add({ + 'litUserMainEndVS': depthClamp + }); + + material.shaderChunks.wgsl.add({ + 'litUserMainEndVS': depthClampWGSL + }); + + material.update(); + return material; + } + + initialize() { + // Ensure static resources are initialized + Annotation._initializeStatic(this.app); + + // Create texture + this.texture = Annotation._createHotspotTexture(this.app, this.label); + + // Create the base and overlay material + this.materials = [ + Annotation._createHotspotMaterial(this.texture, { + opacity: 1, + depthTest: true, + depthWrite: true + }), + Annotation._createHotspotMaterial(this.texture, { + opacity: 0.25, + depthTest: false, + depthWrite: false + }) + ]; + + const base = new Entity('base'); + const baseMi = new MeshInstance(Annotation.mesh, this.materials[0]); + baseMi.cull = false; + base.addComponent('render', { + layers: [Annotation.layers[0].id], + meshInstances: [baseMi] + }); + + const overlay = new Entity('overlay'); + const overlayMi = new MeshInstance(Annotation.mesh, this.materials[1]); + overlayMi.cull = false; + overlay.addComponent('render', { + layers: [Annotation.layers[1].id], + meshInstances: [overlayMi] + }); + + this.entity.addChild(base); + this.entity.addChild(overlay); + + // Create hotspot dom + this.hotspotDom = document.createElement('div'); + this.hotspotDom.className = 'pc-annotation-hotspot'; + + // Add click handlers + this.hotspotDom.addEventListener('click', (e) => { + e.stopPropagation(); + this.showTooltip(); + }); + + const leave = () => { + if (Annotation.hoverAnnotation === this) { + Annotation.hoverAnnotation = null; + this.setHover(false); + } + }; + + const enter = () => { + if (Annotation.hoverAnnotation !== null) { + Annotation.hoverAnnotation.setHover(false); + } + Annotation.hoverAnnotation = this; + this.setHover(true); + }; + + this.hotspotDom.addEventListener('pointerenter', enter); + this.hotspotDom.addEventListener('pointerleave', leave); + + document.addEventListener('click', () => { + this.hideTooltip(); + }); + + Annotation.parentDom.appendChild(this.hotspotDom); + + // Store prerender handler for cleanup + const prerenderHandler = () => { + if (!Annotation.camera) return; + + const position = this.entity.getPosition(); + const screenPos = Annotation.camera.camera.worldToScreen(position); + + const { viewMatrix } = Annotation.camera.camera; + viewMatrix.transformPoint(position, vec); + if (vec.z >= 0) { + this._hideElements(); + return; + } + + this._updatePositions(screenPos); + this._updateRotationAndScale(); + + // update material opacity and also directly on the uniform so we + // can avoid a full material update + this.materials[0].opacity = Annotation.opacity; + this.materials[1].opacity = 0.25 * Annotation.opacity; + this.materials[0].setParameter('material_opacity', Annotation.opacity); + this.materials[1].setParameter('material_opacity', 0.25 * Annotation.opacity); + }; + this.app.on('prerender', prerenderHandler); + + // Clean up on entity destruction + this.on('destroy', () => { + this.hotspotDom.remove(); + if (Annotation.activeAnnotation === this) { + this.hideTooltip(); + } + + this.materials.forEach(mat => mat.destroy()); + this.materials = []; + + this.texture.destroy(); + this.texture = null; + + // Remove prerender event listener + this.app.off('prerender', prerenderHandler); + }); + } + + /** + * Set the hover state of the annotation. + * @param {boolean} hover - Whether the annotation is hovered + * @private + */ + setHover(hover) { + this.materials.forEach((material) => { + material.emissive.copy(hover ? Annotation.hoverColor : Annotation.hotspotColor); + material.update(); + }); + this.fire('hover', hover); + } + + /** + * @private + */ + showTooltip() { + Annotation.activeAnnotation = this; + Annotation.tooltipDom.style.visibility = 'visible'; + Annotation.tooltipDom.style.opacity = '1'; + Annotation.titleDom.textContent = this.title; + Annotation.textDom.textContent = this.text; + this.fire('show', this); + } + + /** + * @private + */ + hideTooltip() { + Annotation.activeAnnotation = null; + Annotation.tooltipDom.style.opacity = '0'; + + // Wait for fade out before hiding + setTimeout(() => { + if (Annotation.tooltipDom.style.opacity === '0') { + Annotation.tooltipDom.style.visibility = 'hidden'; + } + this.fire('hide'); + }, 200); // Match the transition duration + } + + /** + * Hide all elements when annotation is behind camera. + * @private + */ + _hideElements() { + this.hotspotDom.style.display = 'none'; + if (Annotation.activeAnnotation === this) { + if (Annotation.tooltipDom.style.visibility !== 'hidden') { + this.hideTooltip(); + } + } + } + + /** + * Update screen-space positions of HTML elements. + * @param {Vec3} screenPos - Screen coordinate + * @private + */ + _updatePositions(screenPos) { + // Show and position hotspot + this.hotspotDom.style.display = 'block'; + this.hotspotDom.style.left = `${screenPos.x}px`; + this.hotspotDom.style.top = `${screenPos.y}px`; + + // Position tooltip + if (Annotation.activeAnnotation === this) { + Annotation.tooltipDom.style.left = `${screenPos.x}px`; + Annotation.tooltipDom.style.top = `${screenPos.y}px`; + } + } + + /** + * Update 3D rotation and scale of hotspot planes. + * @private + */ + _updateRotationAndScale() { + // Copy camera rotation to align with view plane + const cameraRotation = Annotation.camera.getRotation(); + this._updateHotspotTransform(this.entity, cameraRotation); + + // Calculate scale based on distance to maintain constant screen size + const scale = this._calculateScreenSpaceScale(); + this.entity.setLocalScale(scale, scale, scale); + } + + /** + * Update rotation of a single hotspot entity. + * @param {Entity} hotspot - The hotspot entity to update + * @param {Quat} cameraRotation - The camera's current rotation + * @private + */ + _updateHotspotTransform(hotspot, cameraRotation) { + hotspot.setRotation(cameraRotation); + hotspot.rotateLocal(90, 0, 0); + } + + /** + * Calculate scale factor to maintain constant screen-space size. + * @returns {number} The scale to apply to hotspot entities + * @private + */ + _calculateScreenSpaceScale() { + const cameraPos = Annotation.camera.getPosition(); + const toAnnotation = this.entity.getPosition().clone().sub(cameraPos); + const distance = toAnnotation.length(); + + // Use the canvas's CSS/client height instead of graphics device height + const canvas = this.app.graphicsDevice.canvas; + const screenHeight = canvas.clientHeight; + + // Get the camera's projection matrix vertical scale factor + const projMatrix = Annotation.camera.camera.projectionMatrix; + const worldSize = (Annotation.hotspotSize / screenHeight) * (2 * distance / projMatrix.data[5]); + + return worldSize; + } +} diff --git a/scripts/esm/camera-controls.mjs b/scripts/esm/camera-controls.mjs index 003d8bdf816..04498a18d9e 100644 --- a/scripts/esm/camera-controls.mjs +++ b/scripts/esm/camera-controls.mjs @@ -738,8 +738,7 @@ class CameraControls extends Script { const orbit = +(this._mode === 'orbit'); const fly = +(this._mode === 'fly'); const double = +(this._state.touches > 1); - const pan = +this.enablePan && - ((orbit && this._state.shift) || this._state.mouse[1] || +(button[1] === -1)); + const desktopPan = +(this._state.shift || this._state.mouse[1]); const mobileJoystick = +(this._flyMobileInput.layout.endsWith('joystick')); // multipliers @@ -757,7 +756,7 @@ class CameraControls extends Script { const keyMove = this._state.axis.clone().normalize(); v.add(keyMove.mulScalar(fly * moveMult)); const panMove = screenToWorld(this._camera, mouse[0], mouse[1], this._pose.distance); - v.add(panMove.mulScalar(orbit * pan)); + v.add(panMove.mulScalar(orbit * desktopPan * +this.enablePan)); const wheelMove = tmpV2.set(0, 0, wheel[0]); v.add(wheelMove.mulScalar(orbit * zoomMult)); deltas.move.append([v.x, v.y, v.z]); @@ -765,7 +764,7 @@ class CameraControls extends Script { // desktop rotate v.set(0, 0, 0); const mouseRotate = tmpV2.set(mouse[0], mouse[1], 0); - v.add(mouseRotate.mulScalar((1 - pan) * rotateMult)); + v.add(mouseRotate.mulScalar((1 - (orbit * desktopPan)) * rotateMult)); deltas.rotate.append([v.x, v.y, v.z]); // mobile move @@ -773,7 +772,7 @@ class CameraControls extends Script { const flyMove = tmpV2.set(leftInput[0], 0, -leftInput[1]); v.add(flyMove.mulScalar(fly * moveMult)); const orbitMove = screenToWorld(this._camera, touch[0], touch[1], this._pose.distance); - v.add(orbitMove.mulScalar(orbit * double)); + v.add(orbitMove.mulScalar(orbit * double * +this.enablePan)); const pinchMove = tmpV2.set(0, 0, pinch[0]); v.add(pinchMove.mulScalar(orbit * double * zoomTouchMult)); deltas.move.append([v.x, v.y, v.z]); diff --git a/scripts/esm/gsplat/gsplat-flipbook.mjs b/scripts/esm/gsplat/gsplat-flipbook.mjs new file mode 100644 index 00000000000..e98bcba5813 --- /dev/null +++ b/scripts/esm/gsplat/gsplat-flipbook.mjs @@ -0,0 +1,491 @@ +import { Script, Asset } from 'playcanvas'; + +/** + * Reference-counted asset cache shared across all GsplatFlipbook instances. + * Ensures assets are only loaded once and properly cleaned up when no longer needed. + */ +class AssetCache { + /** + * Cache storage: Map + * @type {Map} + */ + static cache = new Map(); + + /** + * Get an asset from cache or create and load it. + * @param {string} url - The asset URL + * @param {import('playcanvas').AppBase} app - The application instance + * @returns {import('playcanvas').Asset} The asset + */ + static getAsset(url, app) { + const entry = this.cache.get(url); + + if (entry) { + // Asset exists in cache, increment reference count + entry.refCount++; + return entry.asset; + } + + // Create new asset + // disable reorder to avoid reordering of splats for rendering performance due to high reordering cost + // for our purpose (applied to ply files only, ignored for other formats) + const asset = new Asset(url, 'gsplat', { url }, { reorder: false }); + app.assets.add(asset); + app.assets.load(asset); + + // Add to cache with initial refCount of 1 + this.cache.set(url, { asset, refCount: 1 }); + + return asset; + } + + /** + * Release an asset from cache, decrementing its reference count. + * Actual unload happens in processPendingUnloads() when both the cache refCount + * and resource.refCount are 0. + * @param {string} url - The asset URL + * @param {import('playcanvas').AppBase} app - The application instance + */ + static releaseAsset(url, app) { + const entry = this.cache.get(url); + + if (!entry) return; + + entry.refCount--; + } + + /** + * Process pending unloads, checking if resources are safe to unload. + * This should be called periodically (e.g., in update loop) to clean up resources + * that are no longer referenced by any script instances or GSplatManager. + * @param {import('playcanvas').AppBase} app - The application instance + */ + static processPendingUnloads(app) { + for (const [url, entry] of this.cache.entries()) { + if (entry.refCount <= 0 && entry.asset.resource) { + // Check if resource is still in use by any manager + if (entry.asset.resource.refCount === 0) { + app.assets.remove(entry.asset); + entry.asset.unload(); + this.cache.delete(url); + } + } + } + } +} + +/** @enum {string} */ +const PlayMode = { + Once: 'once', + Loop: 'loop', + Bounce: 'bounce' +}; + +/** + * GSplat Flipbook Script + * + * Plays a sequence of gsplat files as a flipbook animation with automatic asset loading, + * unloading, and reference-counted caching. Multiple script instances share a common cache + * to minimize memory usage when using the same assets. Preloads multiple frames ahead for + * smooth playback even with variable loading times. + * + * ## Requirements + * + * The entity must have a gsplat component with `unified: true` added **before** this script. + * + * ## Usage + * + * @example + * // Create entity and add gsplat component first + * const entity = new pc.Entity('Flipbook'); + * entity.addComponent('gsplat', { + * unified: true + * }); + * + * // Add script component and create flipbook script + * entity.addComponent('script'); + * const flipbook = entity.script.create(GsplatFlipbook); + * + * // Configure attributes + * flipbook.fps = 30; + * flipbook.folder = 'assets/splats/animation'; + * flipbook.filenamePattern = 'frame_{frame:04}.sog'; + * flipbook.startFrame = 1; + * flipbook.endFrame = 100; + * flipbook.playMode = 'loop'; + * flipbook.playing = true; + * flipbook.preloadCount = 10; // Preload 10 frames ahead (default) + * + * // Add to scene + * app.root.addChild(entity); + * + * @example + * // Control playback at runtime + * flipbook.pause(); // Pause animation + * flipbook.play(); // Resume animation + * flipbook.stop(); // Stop and reset to start + * flipbook.seekToFrame(50); // Jump to specific frame + * + * @example + * // Different filename patterns + * // Zero-padded: 'wave_{frame:04}.sog' -> wave_0001.sog, wave_0002.sog + * // Two digits: 'anim{frame:02}.sog' -> anim01.sog, anim02.sog + * // No padding: 'file_{frame}.sog' -> file_1.sog, file_2.sog + * + * @example + * // Adjust preload buffer for different scenarios + * flipbook.preloadCount = 20; // Larger buffer for slower connections + * flipbook.preloadCount = 5; // Smaller buffer to save memory + */ +class GsplatFlipbook extends Script { + static scriptName = 'gsplatFlipbook'; + + /** + * Frames per second for playback. + * @attribute + * @type {number} + */ + fps = 30; + + /** + * Base folder path for assets (e.g., 'assets/splats/wave/'). + * @attribute + * @type {string} + */ + folder = ''; + + /** + * Filename pattern with {frame} or {frame:NN} placeholder. + * Examples: 'wave_{frame:04}.sog' -> wave_0001.sog, 'frame{frame:02}.sog' -> frame01.sog + * @attribute + * @type {string} + */ + filenamePattern = 'frame_{frame:04}.sog'; + + /** + * First frame number. + * @attribute + * @type {number} + */ + startFrame = 1; + + /** + * Last frame number. + * @attribute + * @type {number} + */ + endFrame = 100; + + /** + * Playback mode: 'once' (play once and stop), 'loop' (wrap around), or 'bounce' (reverse at ends). + * @attribute + * @type {PlayMode} + */ + playMode = PlayMode.Loop; + + /** + * Whether the animation is currently playing (can be toggled to pause/resume). + * @attribute + * @type {boolean} + */ + playing = true; + + /** + * Number of frames to preload ahead for smooth playback (default: 10). + * Higher values provide smoother playback but use more memory. + * Preloaded assets are shared across all script instances via AssetCache. + * @attribute + * @type {number} + */ + preloadCount = 10; + + initialize() { + // Internal state + this.currentFrame = this.startFrame; + this.frameTime = 0; + this.direction = 1; // 1 for forward, -1 for reverse (used in bounce mode) + this.currentAsset = null; + this.currentAssetUrl = null; + // Array of preloaded frame entries: [{ frameNum, url, asset }, ...] + this.preloadedFrames = []; + + // Verify gsplat component exists (should be added before this script) + if (!this.entity.gsplat) { + console.error('GsplatFlipbook: Entity must have a gsplat component with unified=true'); + return; + } + + // Load first frame + this.loadFrame(this.currentFrame); + } + + update(dt) { + if (!this.playing) return; + + this.frameTime += dt; + + // Check if it's time to advance frame + if (this.frameTime >= 1 / this.fps) { + this.frameTime = 0; + + // Check if next asset is ready + if (this.preloadedFrames.length > 0 && this.preloadedFrames[0].asset.loaded) { + this.switchToNextFrame(); + } + } + + // Process pending unloads - check if any assets are safe to unload + AssetCache.processPendingUnloads(this.app); + } + + /** + * Switch to the next preloaded frame + * @private + */ + switchToNextFrame() { + // Get first preloaded frame + const nextFrame = this.preloadedFrames.shift(); + if (!nextFrame) return; + + // Release old asset + if (this.currentAssetUrl) { + AssetCache.releaseAsset(this.currentAssetUrl, this.app); + } + + // Set new asset on component + if (this.entity.gsplat) { + this.entity.gsplat.asset = nextFrame.asset; + } + + // Update current references + this.currentAsset = nextFrame.asset; + this.currentAssetUrl = nextFrame.url; + + // Advance frame + this.advanceFrame(); + + // Maintain preload buffer + this.maintainPreloadBuffer(); + } + + /** + * Advance to the next frame based on playMode + * @private + */ + advanceFrame() { + if (this.playMode === 'bounce') { + this.currentFrame += this.direction; + + // Check bounds and reverse direction + if (this.currentFrame >= this.endFrame) { + this.currentFrame = this.endFrame; + this.direction = -1; + } else if (this.currentFrame <= this.startFrame) { + this.currentFrame = this.startFrame; + this.direction = 1; + } + } else if (this.playMode === 'loop') { + this.currentFrame++; + if (this.currentFrame > this.endFrame) { + this.currentFrame = this.startFrame; + } + } else if (this.playMode === 'once') { + this.currentFrame++; + if (this.currentFrame > this.endFrame) { + this.playing = false; + } + } + } + + /** + * Get the next frame number without modifying state + * @private + * @returns {number|null} Next frame number or null if animation is done + */ + getNextFrameNumber() { + if (this.playMode === 'bounce') { + return this.currentFrame + this.direction; + } else if (this.playMode === 'loop') { + const next = this.currentFrame + 1; + return next > this.endFrame ? this.startFrame : next; + } else if (this.playMode === 'once') { + const next = this.currentFrame + 1; + return next <= this.endFrame ? next : null; + } + return null; + } + + /** + * Get the next frame number from the last preloaded frame + * @private + * @returns {number|null} Next frame number or null if animation is done + */ + getNextFrameNumberFromLast() { + // If no frames preloaded, use current frame as base + if (this.preloadedFrames.length === 0) { + return this.getNextFrameNumber(); + } + + // Get last preloaded frame + const lastFrame = this.preloadedFrames[this.preloadedFrames.length - 1].frameNum; + + if (this.playMode === 'bounce') { + // Simulate direction changes from current to last frame + let checkFrame = this.currentFrame; + let checkDir = this.direction; + + while (checkFrame !== lastFrame) { + checkFrame += checkDir; + if (checkFrame >= this.endFrame) { + checkFrame = this.endFrame; + checkDir = -1; + } else if (checkFrame <= this.startFrame) { + checkFrame = this.startFrame; + checkDir = 1; + } + } + + // Now calculate next from last frame + const next = lastFrame + checkDir; + if (next >= this.startFrame && next <= this.endFrame) { + return next; + } + // If at boundary, reverse direction + if (next > this.endFrame) return this.endFrame; + if (next < this.startFrame) return this.startFrame; + return next; + } else if (this.playMode === 'loop') { + const next = lastFrame + 1; + return next > this.endFrame ? this.startFrame : next; + } else if (this.playMode === 'once') { + const next = lastFrame + 1; + return next <= this.endFrame ? next : null; + } + return null; + } + + /** + * Maintain the preload buffer by filling it up to preloadCount frames ahead + * @private + */ + maintainPreloadBuffer() { + // Fill buffer up to preloadCount frames ahead + while (this.preloadedFrames.length < this.preloadCount) { + const nextFrameNum = this.getNextFrameNumberFromLast(); + if (nextFrameNum === null) break; // End of sequence + + const url = this.getFramePath(nextFrameNum); + const asset = AssetCache.getAsset(url, this.app); + this.preloadedFrames.push({ frameNum: nextFrameNum, url, asset }); + } + } + + /** + * Load and set a specific frame + * @param {number} frameNum - Frame number to load + * @private + */ + loadFrame(frameNum) { + const url = this.getFramePath(frameNum); + const asset = AssetCache.getAsset(url, this.app); + + this.currentAssetUrl = url; + this.currentAsset = asset; + + // Set asset when loaded + if (asset.loaded) { + if (this.entity.gsplat) { + this.entity.gsplat.asset = asset; + } + // Fill preload buffer + this.maintainPreloadBuffer(); + } else { + asset.once('load', () => { + if (this.entity.gsplat) { + this.entity.gsplat.asset = asset; + } + // Fill preload buffer + this.maintainPreloadBuffer(); + }); + } + } + + /** + * Construct the full path for a frame + * @param {number} frameNum - Frame number + * @returns {string} Full asset URL + * @private + */ + getFramePath(frameNum) { + let filename = this.filenamePattern; + + // Replace {frame:NN} or {frame} with padded number + filename = filename.replace(/\{frame(?::(\d+))?\}/g, (match, padding) => { + if (padding) { + return frameNum.toString().padStart(parseInt(padding, 10), '0'); + } + return frameNum.toString(); + }); + + // Combine folder and filename + let path = this.folder; + if (path && !path.endsWith('/')) { + path += '/'; + } + return path + filename; + } + + /** + * Start or resume playback + */ + play() { + this.playing = true; + } + + /** + * Pause playback + */ + pause() { + this.playing = false; + } + + /** + * Stop playback and reset to start frame + */ + stop() { + this.playing = false; + this.currentFrame = this.startFrame; + this.direction = 1; + this.frameTime = 0; + this.loadFrame(this.currentFrame); + } + + /** + * Seek to a specific frame + * @param {number} frameNum - Frame number to seek to + */ + seekToFrame(frameNum) { + if (frameNum < this.startFrame || frameNum > this.endFrame) { + console.warn(`Frame ${frameNum} is out of range [${this.startFrame}, ${this.endFrame}]`); + return; + } + + this.currentFrame = frameNum; + this.frameTime = 0; + this.loadFrame(this.currentFrame); + } + + onDestroy() { + // Release current asset + if (this.currentAssetUrl) { + AssetCache.releaseAsset(this.currentAssetUrl, this.app); + } + // Release all preloaded assets + for (const frame of this.preloadedFrames) { + AssetCache.releaseAsset(frame.url, this.app); + } + this.preloadedFrames = []; + } +} + +export { GsplatFlipbook }; diff --git a/scripts/esm/gsplat/gsplat-shader-effect.mjs b/scripts/esm/gsplat/gsplat-shader-effect.mjs index 23255b4690e..634468d3da7 100644 --- a/scripts/esm/gsplat/gsplat-shader-effect.mjs +++ b/scripts/esm/gsplat/gsplat-shader-effect.mjs @@ -14,9 +14,8 @@ import { Script } from 'playcanvas'; * `entity.gsplat.material` and applies shader customizations immediately or when the asset loads. * * **Unified Mode (`unified=true`):** - * Multiple gsplat components share materials per camera/layer combination. Materials are created - * during the first frame render. The script listens to the 'material:created' event for immediate - * notification and retries each frame as fallback to ensure materials are applied. + * Multiple gsplat components share a template material accessible via `app.scene.gsplat.material`. + * The script applies shader customizations to this template material. * * **Enable/Disable:** * When enabled, the shader effect is applied and effectTime starts tracking from 0. @@ -32,14 +31,6 @@ import { Script } from 'playcanvas'; class GsplatShaderEffect extends Script { static scriptName = 'gsplatShaderEffect'; - /** - * Optional camera entity to target in unified mode. If not set, applies to all cameras. - * - * @attribute - * @type {import('playcanvas').Entity | null} - */ - camera = null; - /** * Time since effect was enabled * @type {number} @@ -47,15 +38,15 @@ class GsplatShaderEffect extends Script { effectTime = 0; /** - * Set of materials with applied shader - * @type {Set} + * The material this effect is applied to + * @type {import('playcanvas').Material | null} */ - materialsApplied = new Set(); + material = null; initialize() { this.initialized = false; this.effectTime = 0; - this.materialsApplied.clear(); + this.material = null; this.shadersNeedApplication = false; // Listen to enable/disable events @@ -81,10 +72,6 @@ class GsplatShaderEffect extends Script { this.removeShaders(); }); - // Register event listener immediately for unified mode to catch materials created on first frame - // This is safe to call even if the gsplat component doesn't exist yet - this.setupUnifiedEventListener(); - if (!this.entity.gsplat) { // gsplat component not yet available, will retry each frame return; @@ -100,7 +87,7 @@ class GsplatShaderEffect extends Script { applyShaders() { if (this.entity.gsplat?.unified) { - // Unified mode: Apply to specified camera (or all cameras if not specified) + // Unified mode: Apply to template material this.applyToUnifiedMaterials(); } else { // Non-unified mode: Apply to component's material @@ -109,63 +96,24 @@ class GsplatShaderEffect extends Script { } removeShaders() { - if (this.materialsApplied.size === 0) return; + if (!this.material) return; const device = this.app.graphicsDevice; const shaderLanguage = device?.isWebGPU ? 'wgsl' : 'glsl'; - // Remove custom shader chunk from all materials - this.materialsApplied.forEach((material) => { - material.getShaderChunks(shaderLanguage).delete('gsplatCustomizeVS'); - material.update(); - }); - - // Clear the set and stop tracking - this.materialsApplied.clear(); - } - - setupUnifiedEventListener() { - // Only set up once - if (this._materialCreatedHandler) return; - - // @ts-ignore - gsplat system exists at runtime - const gsplatSystem = this.app.systems.gsplat; - - // Set up event listener - this._materialCreatedHandler = (material, camera, layer) => { - // Only apply if enabled - if (!this.enabled) return; - - // Apply shader immediately when material is created - // The gsplat component may not be fully initialized yet, so we can't check it here - if (!this.materialsApplied.has(material)) { - // Check camera filter if specified - if (this.camera && this.camera.camera && this.camera.camera.camera !== camera) { - return; - } - - this.applyShaderToMaterial(material); - this.materialsApplied.add(material); - - // Store layer info for potential validation later - if (!this._materialLayers) { - this._materialLayers = new Map(); - } - this._materialLayers.set(material, layer.id); - } - }; - - gsplatSystem.on('material:created', this._materialCreatedHandler); + this.material.getShaderChunks(shaderLanguage).delete('gsplatModifyVS'); + this.material.update(); + this.material = null; } applyToComponentMaterial() { const applyShader = () => { - const material = this.entity.gsplat?.material; - if (!material) { + this.material = this.entity.gsplat?.material ?? null; + if (!this.material) { console.error(`${this.constructor.name}: gsplat material not available.`); return; } - this.applyShaderToMaterial(material); + this.applyShaderToMaterial(this.material); }; if (this.entity.gsplat?.material) { @@ -177,58 +125,13 @@ class GsplatShaderEffect extends Script { } applyToUnifiedMaterials() { - // Try to apply immediately to any existing materials - this.updateUnifiedMaterials(); - - // If no materials yet, set retry flag (event listener is already set up) - if (this.materialsApplied.size === 0) { - this.needsRetry = true; - } - } - - updateUnifiedMaterials() { - // @ts-ignore - gsplat system exists at runtime - const gsplatSystem = this.app.systems.gsplat; - const scene = this.app.scene; - const composition = scene.layers; - - // Get all layers this component is on - const componentLayers = this.entity.gsplat?.layers; - if (!componentLayers) return; - - // Determine which cameras to target - let targetCameras; - const cam = this.camera?.camera?.camera; - if (cam) { - // Specific camera specified via attribute - targetCameras = [cam]; - } else { - // All cameras in the composition - targetCameras = composition.cameras.map(cameraComponent => cameraComponent.camera); + this.material = this.app.scene.gsplat?.material ?? null; + if (!this.material) { + console.warn(`${this.constructor.name}: gsplat template material not available.`); + return; } - // Iterate through target cameras (already Camera objects, not CameraComponents) - targetCameras.forEach((camera) => { - // For each layer this component is on - componentLayers.forEach((layerId) => { - // Check if this camera renders this layer - if (camera.layers.indexOf(layerId) >= 0) { - const layer = composition.getLayerById(layerId); - if (layer) { - const material = gsplatSystem.getGSplatMaterial(camera, layer); - if (material && !this.materialsApplied.has(material)) { - this.applyShaderToMaterial(material); - this.materialsApplied.add(material); - } - } - } - }); - }); - - if (this.materialsApplied.size > 0) { - this.needsRetry = false; - // Keep event listener active to catch any new materials created later - } + this.applyShaderToMaterial(this.material); } applyShaderToMaterial(material) { @@ -236,7 +139,7 @@ class GsplatShaderEffect extends Script { const shaderLanguage = device?.isWebGPU ? 'wgsl' : 'glsl'; const customShader = shaderLanguage === 'wgsl' ? this.getShaderWGSL() : this.getShaderGLSL(); - material.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', customShader); + material.getShaderChunks(shaderLanguage).set('gsplatModifyVS', customShader); material.update(); } @@ -260,30 +163,24 @@ class GsplatShaderEffect extends Script { this.shadersNeedApplication = false; } - // Retry applying to unified materials if needed - if (this.entity.gsplat?.unified && this.needsRetry) { - this.updateUnifiedMaterials(); - } - - if (this.materialsApplied.size === 0) return; + if (!this.material) return; // Update time this.effectTime += dt; // Let subclass update the effect this.updateEffect(this.effectTime, dt); + + // Update material after all parameters have been set (if still valid) + // Note: material may be set to null by removeShaders() if effect disables itself + if (this.material) { + this.material.update(); + } } destroy() { // Remove shaders if they're still applied this.removeShaders(); - - // Clean up event listener - if (this._materialCreatedHandler) { - // @ts-ignore - gsplat system exists at runtime - this.app.systems.gsplat.off('material:created', this._materialCreatedHandler); - this._materialCreatedHandler = null; - } } /** @@ -307,14 +204,12 @@ class GsplatShaderEffect extends Script { } /** - * Set a uniform value on all applied materials. + * Set a uniform value on the material. * @param {string} name - The uniform name * @param {*} value - The uniform value */ setUniform(name, value) { - this.materialsApplied.forEach((material) => { - material.setParameter(name, value); - }); + this.material?.setParameter(name, value); } /** diff --git a/scripts/esm/gsplat/reveal-grid-eruption.mjs b/scripts/esm/gsplat/reveal-grid-eruption.mjs index 801d4dcc9ed..6d20b7fccb3 100644 --- a/scripts/esm/gsplat/reveal-grid-eruption.mjs +++ b/scripts/esm/gsplat/reveal-grid-eruption.mjs @@ -34,7 +34,7 @@ void initShared(vec3 center) { g_tEnd = g_tStart + uDuration; } -void modifyCenter(inout vec3 center) { +void modifySplatCenter(inout vec3 center) { vec3 originalCenter = center; initShared(center); @@ -59,47 +59,47 @@ void modifyCenter(inout vec3 center) { // After movement: original position (no change needed) } -void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { // Check if animation is complete (after landing transition) float timeSinceLanding = uTime - g_tEnd; if (timeSinceLanding >= 0.3) return; // Effect complete, no modifications // Early exit for distant splats during animation if (g_blockDist > uEndRadius) { - gsplatMakeRound(covA, covB, 0.0); + scale = vec3(0.0); return; } // Before movement: invisible if (uTime < g_tStart) { - gsplatMakeRound(covA, covB, 0.0); + scale = vec3(0.0); return; } + // Store original scale for shape preservation + vec3 origScale = scale; + float origSize = gsplatGetSizeFromScale(scale); + // During landing transition after movement if (timeSinceLanding < 0.3) { - float originalSize = gsplatExtractSize(covA, covB); - if (timeSinceLanding < 0.0) { - // During movement: small round dots - gsplatMakeRound(covA, covB, min(uDotSize, originalSize)); + // During movement: small spherical dots + float targetSize = min(uDotSize, origSize); + gsplatMakeSpherical(scale, targetSize); } else { // Landing transition: lerp from dots to original over 0.3s float t = timeSinceLanding * 3.333333; // normalize [0, 0.3] to [0, 1] - float size = mix(uDotSize, originalSize, t); + float size = mix(uDotSize, origSize, t); - // Lerp between round and original shape - vec3 origCovA = covA; - vec3 origCovB = covB; - gsplatMakeRound(covA, covB, size); - covA = mix(covA, origCovA, t); - covB = mix(covB, origCovB, t); + // Lerp between spherical (uniform) and original scale + vec3 sphericalScale = vec3(size); + scale = mix(sphericalScale, origScale, t); } } // After transition: original shape/size (no-op) } -void modifyColor(vec3 center, inout vec4 color) { +void modifySplatColor(vec3 center, inout vec4 color) { // Check if animation is complete float timeSinceLanding = uTime - g_tEnd; if (timeSinceLanding >= uLandDuration) return; // Effect complete, no modifications @@ -155,7 +155,7 @@ fn initShared(center: vec3f) { g_tEnd = g_tStart + uniform.uDuration; } -fn modifyCenter(center: ptr) { +fn modifySplatCenter(center: ptr) { let originalCenter = *center; initShared(*center); @@ -184,7 +184,7 @@ fn modifyCenter(center: ptr) { // After movement: original position (no change needed) } -fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr, covB: ptr) { +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { // Check if animation is complete (after landing transition) let timeSinceLanding = uniform.uTime - g_tEnd; if (timeSinceLanding >= 0.3) { @@ -193,40 +193,40 @@ fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr uniform.uEndRadius) { - gsplatMakeRound(covA, covB, 0.0); + *scale = vec3f(0.0); return; } // Before movement: invisible if (uniform.uTime < g_tStart) { - gsplatMakeRound(covA, covB, 0.0); + *scale = vec3f(0.0); return; } + // Store original scale for shape preservation + let origScale = *scale; + let origSize = gsplatGetSizeFromScale(*scale); + // During landing transition after movement if (timeSinceLanding < 0.3) { - let originalSize = gsplatExtractSize(*covA, *covB); - if (timeSinceLanding < 0.0) { - // During movement: small round dots - gsplatMakeRound(covA, covB, min(uniform.uDotSize, originalSize)); + // During movement: small spherical dots + let targetSize = min(uniform.uDotSize, origSize); + gsplatMakeSpherical(scale, targetSize); } else { // Landing transition: lerp from dots to original over 0.3s let t = timeSinceLanding * 3.333333; // normalize [0, 0.3] to [0, 1] - let size = mix(uniform.uDotSize, originalSize, t); + let size = mix(uniform.uDotSize, origSize, t); - // Lerp between round and original shape - let origCovA = *covA; - let origCovB = *covB; - gsplatMakeRound(covA, covB, size); - *covA = mix(*covA, origCovA, t); - *covB = mix(*covB, origCovB, t); + // Lerp between spherical (uniform) and original scale + let sphericalScale = vec3f(size); + *scale = mix(sphericalScale, origScale, t); } } // After transition: original shape/size (no-op) } -fn modifyColor(center: vec3f, color: ptr) { +fn modifySplatColor(center: vec3f, color: ptr) { // Check if animation is complete let timeSinceLanding = uniform.uTime - g_tEnd; if (timeSinceLanding >= uniform.uLandDuration) { diff --git a/scripts/esm/gsplat/reveal-radial.mjs b/scripts/esm/gsplat/reveal-radial.mjs index 40e7f6dc7ef..ba103b1476e 100644 --- a/scripts/esm/gsplat/reveal-radial.mjs +++ b/scripts/esm/gsplat/reveal-radial.mjs @@ -30,7 +30,7 @@ float hash(vec3 p) { return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); } -void modifyCenter(inout vec3 center) { +void modifySplatCenter(inout vec3 center) { initShared(center); // Early exit optimization @@ -54,60 +54,61 @@ void modifyCenter(inout vec3 center) { } } -void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { // Early exit for distant splats - hide them if (g_dist > uEndRadius) { - gsplatMakeRound(covA, covB, 0.0); + scale = vec3(0.0); return; } - // Determine scale and phase - float scale; + // Store original scale for shape preservation + vec3 origScale = scale; + float origSize = gsplatGetSizeFromScale(scale); + + // Determine scale factor and phase + float scaleFactor; bool isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist; if (isLiftWave) { // Lift wave: transition from dots to full size - scale = (g_liftWavePos >= g_dist + 2.0) ? 1.0 : mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5); + scaleFactor = (g_liftWavePos >= g_dist + 2.0) ? 1.0 : mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5); } else if (g_dist > g_dotWavePos + 1.0) { // Before dot wave: invisible - gsplatMakeRound(covA, covB, 0.0); + scale = vec3(0.0); return; } else if (g_dist > g_dotWavePos - 1.0) { // Dot wave front: scale from 0 to 0.1 with 2x peak at center float distToWave = abs(g_dist - g_dotWavePos); - scale = (distToWave < 0.5) + scaleFactor = (distToWave < 0.5) ? mix(0.1, 0.2, 1.0 - distToWave * 2.0) : mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist)); } else { // After dot wave, before lift: small dots - scale = 0.1; + scaleFactor = 0.1; } - // Apply scale to covariance - if (scale >= 1.0) { + // Apply scale + if (scaleFactor >= 1.0) { // Fully revealed: original shape and size (no-op) return; } else if (isLiftWave) { - // Lift wave: lerp from round dots to original shape - float t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1] - float dotSize = scale * 0.05; - float originalSize = gsplatExtractSize(covA, covB); - float finalSize = mix(dotSize, originalSize, t); + // Lift wave: lerp from spherical dots to original shape + float t = (scaleFactor - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1] + float dotSize = scaleFactor * 0.05; + float finalSize = mix(dotSize, origSize, t); - // Lerp between round and scaled original - vec3 origCovA = covA * (scale * scale); - vec3 origCovB = covB * (scale * scale); - gsplatMakeRound(covA, covB, finalSize); - covA = mix(covA, origCovA, t); - covB = mix(covB, origCovB, t); + // Lerp between spherical (uniform) and scaled original + vec3 sphericalScale = vec3(finalSize); + vec3 scaledOrig = origScale * scaleFactor; + scale = mix(sphericalScale, scaledOrig, t); } else { - // Dot phase: round with absolute size, but don't make small splats larger - float originalSize = gsplatExtractSize(covA, covB); - gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize)); + // Dot phase: spherical with absolute size, but don't make small splats larger + float targetSize = min(scaleFactor * 0.05, origSize); + gsplatMakeSpherical(scale, targetSize); } } -void modifyColor(vec3 center, inout vec4 color) { +void modifySplatColor(vec3 center, inout vec4 color) { // Use shared globals if (g_dist > uEndRadius) return; @@ -155,7 +156,7 @@ fn hash(p: vec3f) -> f32 { return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453); } -fn modifyCenter(center: ptr) { +fn modifySplatCenter(center: ptr) { initShared(*center); // Early exit optimization @@ -181,62 +182,63 @@ fn modifyCenter(center: ptr) { } } -fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr, covB: ptr) { +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { // Early exit for distant splats - hide them if (g_dist > uniform.uEndRadius) { - gsplatMakeRound(covA, covB, 0.0); + *scale = vec3f(0.0); return; } - // Determine scale and phase - var scale: f32; + // Store original scale for shape preservation + let origScale = *scale; + let origSize = gsplatGetSizeFromScale(*scale); + + // Determine scale factor and phase + var scaleFactor: f32; let isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist; if (isLiftWave) { // Lift wave: transition from dots to full size - scale = select(mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5), 1.0, g_liftWavePos >= g_dist + 2.0); + scaleFactor = select(mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5), 1.0, g_liftWavePos >= g_dist + 2.0); } else if (g_dist > g_dotWavePos + 1.0) { // Before dot wave: invisible - gsplatMakeRound(covA, covB, 0.0); + *scale = vec3f(0.0); return; } else if (g_dist > g_dotWavePos - 1.0) { // Dot wave front: scale from 0 to 0.1 with 2x peak at center let distToWave = abs(g_dist - g_dotWavePos); - scale = select( + scaleFactor = select( mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist)), mix(0.1, 0.2, 1.0 - distToWave * 2.0), distToWave < 0.5 ); } else { // After dot wave, before lift: small dots - scale = 0.1; + scaleFactor = 0.1; } - // Apply scale to covariance - if (scale >= 1.0) { + // Apply scale + if (scaleFactor >= 1.0) { // Fully revealed: original shape and size (no-op) return; } else if (isLiftWave) { - // Lift wave: lerp from round dots to original shape - let t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1] - let dotSize = scale * 0.05; - let originalSize = gsplatExtractSize(*covA, *covB); - let finalSize = mix(dotSize, originalSize, t); + // Lift wave: lerp from spherical dots to original shape + let t = (scaleFactor - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1] + let dotSize = scaleFactor * 0.05; + let finalSize = mix(dotSize, origSize, t); - // Lerp between round and scaled original - let origCovA = *covA * (scale * scale); - let origCovB = *covB * (scale * scale); - gsplatMakeRound(covA, covB, finalSize); - *covA = mix(*covA, origCovA, t); - *covB = mix(*covB, origCovB, t); + // Lerp between spherical (uniform) and scaled original + let sphericalScale = vec3f(finalSize); + let scaledOrig = origScale * scaleFactor; + *scale = mix(sphericalScale, scaledOrig, t); } else { - // Dot phase: round with absolute size, but don't make small splats larger - let originalSize = gsplatExtractSize(*covA, *covB); - gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize)); + // Dot phase: spherical with absolute size, but don't make small splats larger + let targetSize = min(scaleFactor * 0.05, origSize); + gsplatMakeSpherical(scale, targetSize); } } -fn modifyColor(center: vec3f, color: ptr) { +fn modifySplatColor(center: vec3f, color: ptr) { // Use shared globals if (g_dist > uniform.uEndRadius) { return; diff --git a/scripts/esm/gsplat/reveal-rain.mjs b/scripts/esm/gsplat/reveal-rain.mjs index 2356ab84fd7..88a9d3396e6 100644 --- a/scripts/esm/gsplat/reveal-rain.mjs +++ b/scripts/esm/gsplat/reveal-rain.mjs @@ -42,7 +42,7 @@ void initShared(vec3 center) { g_tLand = g_tStart + uFlightTime; } -void modifyCenter(inout vec3 center) { +void modifySplatCenter(inout vec3 center) { vec3 originalCenter = center; initShared(center); @@ -76,48 +76,48 @@ void modifyCenter(inout vec3 center) { // If landed: stay at original position (no change needed) } -void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { // Check if animation is complete (after landing transition) float timeSinceLanding = uTime - g_tLand; if (timeSinceLanding >= 0.5) return; // Effect complete, no modifications // Early exit for distant splats during animation if (g_dist3D > uEndRadius) { - gsplatMakeRound(covA, covB, 0.0); + scale = vec3(0.0); return; } // Before 2D wave reaches: invisible if (uTime < g_tStart) { - gsplatMakeRound(covA, covB, 0.0); + scale = vec3(0.0); return; } + // Store original scale for shape preservation + vec3 origScale = scale; + float origSize = gsplatGetSizeFromScale(scale); + // During fall and transition after landing if (timeSinceLanding < 0.5) { // Still falling or transitioning - float originalSize = gsplatExtractSize(covA, covB); - if (timeSinceLanding < 0.0) { - // Falling: small round dots - gsplatMakeRound(covA, covB, min(uRainSize, originalSize)); + // Falling: small spherical dots + float targetSize = min(uRainSize, origSize); + gsplatMakeSpherical(scale, targetSize); } else { // Landing transition: lerp from dots to original over 0.5s float t = timeSinceLanding * 2.0; // normalize [0, 0.5] to [0, 1] - float size = mix(uRainSize, originalSize, t); + float size = mix(uRainSize, origSize, t); - // Lerp between round and original shape - vec3 origCovA = covA; - vec3 origCovB = covB; - gsplatMakeRound(covA, covB, size); - covA = mix(covA, origCovA, t); - covB = mix(covB, origCovB, t); + // Lerp between spherical (uniform) and original scale + vec3 sphericalScale = vec3(size); + scale = mix(sphericalScale, origScale, t); } } // After transition: original shape/size (no-op) } -void modifyColor(vec3 center, inout vec4 color) { +void modifySplatColor(vec3 center, inout vec4 color) { // Check if animation is complete float timeSinceLanding = uTime - g_tLand; if (timeSinceLanding >= uHitDuration) return; // Effect complete, no modifications @@ -181,7 +181,7 @@ fn initShared(center: vec3f) { g_tLand = g_tStart + uniform.uFlightTime; } -fn modifyCenter(center: ptr) { +fn modifySplatCenter(center: ptr) { let originalCenter = *center; initShared(*center); @@ -221,7 +221,7 @@ fn modifyCenter(center: ptr) { // If landed: stay at original position (no change needed) } -fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr, covB: ptr) { +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { // Check if animation is complete (after landing transition) let timeSinceLanding = uniform.uTime - g_tLand; if (timeSinceLanding >= 0.5) { @@ -230,41 +230,41 @@ fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr uniform.uEndRadius) { - gsplatMakeRound(covA, covB, 0.0); + *scale = vec3f(0.0); return; } // Before 2D wave reaches: invisible if (uniform.uTime < g_tStart) { - gsplatMakeRound(covA, covB, 0.0); + *scale = vec3f(0.0); return; } + // Store original scale for shape preservation + let origScale = *scale; + let origSize = gsplatGetSizeFromScale(*scale); + // During fall and transition after landing if (timeSinceLanding < 0.5) { // Still falling or transitioning - let originalSize = gsplatExtractSize(*covA, *covB); - if (timeSinceLanding < 0.0) { - // Falling: small round dots - gsplatMakeRound(covA, covB, min(uniform.uRainSize, originalSize)); + // Falling: small spherical dots + let targetSize = min(uniform.uRainSize, origSize); + gsplatMakeSpherical(scale, targetSize); } else { // Landing transition: lerp from dots to original over 0.5s let t = timeSinceLanding * 2.0; // normalize [0, 0.5] to [0, 1] - let size = mix(uniform.uRainSize, originalSize, t); + let size = mix(uniform.uRainSize, origSize, t); - // Lerp between round and original shape - let origCovA = *covA; - let origCovB = *covB; - gsplatMakeRound(covA, covB, size); - *covA = mix(*covA, origCovA, t); - *covB = mix(*covB, origCovB, t); + // Lerp between spherical (uniform) and original scale + let sphericalScale = vec3f(size); + *scale = mix(sphericalScale, origScale, t); } } // After transition: original shape/size (no-op) } -fn modifyColor(center: vec3f, color: ptr) { +fn modifySplatColor(center: vec3f, color: ptr) { // Check if animation is complete let timeSinceLanding = uniform.uTime - g_tLand; if (timeSinceLanding >= uniform.uHitDuration) { diff --git a/scripts/esm/gsplat/shader-effect-box.mjs b/scripts/esm/gsplat/shader-effect-box.mjs index 17385909068..f4464bcd440 100644 --- a/scripts/esm/gsplat/shader-effect-box.mjs +++ b/scripts/esm/gsplat/shader-effect-box.mjs @@ -11,7 +11,7 @@ uniform vec3 uDirection; bool g_insideAABB; uvec4 g_lutValue; -void modifyCenter(inout vec3 center) { +void modifySplatCenter(inout vec3 center) { // Check if splat is inside AABB g_insideAABB = all(greaterThanEqual(center, uAabbMin)) && all(lessThanEqual(center, uAabbMax)); @@ -39,26 +39,25 @@ void modifyCenter(inout vec3 center) { g_lutValue = texelFetch(uLUT, ivec2(texelX, 0), 0); } -void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { if (!g_insideAABB) return; // Unpack scale from alpha channel (unpack 16-bit uint to half-float) - float scale = unpackHalf2x16(g_lutValue.a).x; + float lutScale = unpackHalf2x16(g_lutValue.a).x; // If scale is 0, make invisible - if (scale < 0.01) { - gsplatMakeRound(covA, covB, 0.0); + if (lutScale < 0.01) { + scale = vec3(0.0); return; } - // If scale is less than 1, progressively scale the covariance - if (scale < 1.0) { - covA *= scale; - covB *= scale; + // If scale is less than 1, progressively scale + if (lutScale < 1.0) { + scale *= lutScale; } } -void modifyColor(vec3 center, inout vec4 color) { +void modifySplatColor(vec3 center, inout vec4 color) { if (!g_insideAABB) return; // Unpack tint from RGB channels (unpack 16-bit uints to half-floats) @@ -82,7 +81,7 @@ uniform uDirection: vec3f; var g_insideAABB: bool; var g_lutValue: vec4u; -fn modifyCenter(center: ptr) { +fn modifySplatCenter(center: ptr) { // Check if splat is inside AABB g_insideAABB = all((*center) >= uniform.uAabbMin) && all((*center) <= uniform.uAabbMax); @@ -110,26 +109,25 @@ fn modifyCenter(center: ptr) { g_lutValue = textureLoad(uLUT, vec2i(texelX, 0), 0); } -fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr, covB: ptr) { +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { if (!g_insideAABB) { return; } // Unpack scale from alpha channel (unpack 16-bit uint to half-float) - let scale = unpack2x16float(g_lutValue.a).x; + let lutScale = unpack2x16float(g_lutValue.a).x; // If scale is 0, make invisible - if (scale < 0.01) { - gsplatMakeRound(covA, covB, 0.0); + if (lutScale < 0.01) { + *scale = vec3f(0.0); return; } - // If scale is less than 1, progressively scale the covariance - if (scale < 1.0) { - (*covA) = (*covA) * scale; - (*covB) = (*covB) * scale; + // If scale is less than 1, progressively scale + if (lutScale < 1.0) { + *scale = (*scale) * lutScale; } } -fn modifyColor(center: vec3f, color: ptr) { +fn modifySplatColor(center: vec3f, color: ptr) { if (!g_insideAABB) { return; } // Unpack tint from RGB channels (unpack 16-bit uints to half-floats) diff --git a/scripts/esm/gsplat/shader-effect-crop.mjs b/scripts/esm/gsplat/shader-effect-crop.mjs new file mode 100644 index 00000000000..ef666741d5e --- /dev/null +++ b/scripts/esm/gsplat/shader-effect-crop.mjs @@ -0,0 +1,170 @@ +import { Vec3 } from 'playcanvas'; +import { GsplatShaderEffect } from './gsplat-shader-effect.mjs'; + +const shaderGLSL = /* glsl */` +uniform vec3 uAabbMin; +uniform vec3 uAabbMax; +uniform float uEdgeScaleFactor; + +void modifySplatCenter(inout vec3 center) { + // No modifications needed +} + +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { + // Check if splat is inside AABB + bool insideAABB = all(greaterThanEqual(modifiedCenter, uAabbMin)) && all(lessThanEqual(modifiedCenter, uAabbMax)); + + // If outside AABB, make invisible by scaling to 0 + if (!insideAABB) { + scale = vec3(0.0); + return; + } + + #ifdef GSPLAT_PRECISE_CROP + // Conservative bound: use length(scale) * 3.0 (3 sigma) + // This gives the maximum possible extent + float maxRadius = length(scale) * 3.0; + + // Find minimum distance to any AABB face + vec3 distToMin = modifiedCenter - uAabbMin; + vec3 distToMax = uAabbMax - modifiedCenter; + float minDist = min( + min(min(distToMin.x, distToMin.y), distToMin.z), + min(min(distToMax.x, distToMax.y), distToMax.z) + ); + + // Scale if splat would exceed boundary + if (maxRadius > minDist) { + float s = (minDist / maxRadius) * uEdgeScaleFactor; + scale *= s; + } + #endif +} + +void modifySplatColor(vec3 center, inout vec4 color) { + // No color modification needed +} +`; + +const shaderWGSL = /* wgsl */` +uniform uAabbMin: vec3f; +uniform uAabbMax: vec3f; +uniform uEdgeScaleFactor: f32; + +fn modifySplatCenter(center: ptr) { + // No modifications needed +} + +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { + // Check if splat is inside AABB + let insideAABB = all(modifiedCenter >= uniform.uAabbMin) && all(modifiedCenter <= uniform.uAabbMax); + + // If outside AABB, make invisible by scaling to 0 + if (!insideAABB) { + *scale = vec3f(0.0); + return; + } + + #if GSPLAT_PRECISE_CROP + // Conservative bound: use length(scale) * 3.0 (3 sigma) + // This gives the maximum possible extent + let maxRadius = length(*scale) * 3.0; + + // Find minimum distance to any AABB face + let distToMin = modifiedCenter - uniform.uAabbMin; + let distToMax = uniform.uAabbMax - modifiedCenter; + let minDist = min( + min(min(distToMin.x, distToMin.y), distToMin.z), + min(min(distToMax.x, distToMax.y), distToMax.z) + ); + + // Scale if splat would exceed boundary + if (maxRadius > minDist) { + let s = (minDist / maxRadius) * uniform.uEdgeScaleFactor; + *scale = (*scale) * s; + } + #endif +} + +fn modifySplatColor(center: vec3f, color: ptr) { + // No color modification needed +} +`; + +/** + * Crop shader effect for gaussian splats. + * Drops all splats outside the specified AABB by scaling them to 0. + * + * When GSPLAT_PRECISE_CROP is defined on the material, also scales down splats near the edges + * based on their scale so they don't extend beyond the boundary. + * Uses a conservative estimate based on the length of the scale vector (3 standard deviations). + * + * The edgeScaleFactor attribute controls how aggressively edge splats are scaled down: + * - 1.0 = minimal scaling (just enough to touch the edge) + * - 0.5 = more aggressive scaling (default, reduces size by additional 50%) + * - Lower values = more aggressive scaling + * + * @example + * // Add the script to a gsplat entity + * entity.addComponent('script'); + * const cropScript = entity.script.create(GsplatCropShaderEffect); + * cropScript.aabbMin.set(-1, -1, -1); + * cropScript.aabbMax.set(1, 1, 1); + * cropScript.edgeScaleFactor = 0.5; // Default value + * + * // Enable precise cropping + * material.setDefine('GSPLAT_PRECISE_CROP', ''); + * material.update(); + */ +class GsplatCropShaderEffect extends GsplatShaderEffect { + static scriptName = 'gsplatCropShaderEffect'; + + // Reusable arrays for uniform updates + _aabbMinArray = [0, 0, 0]; + + _aabbMaxArray = [0, 0, 0]; + + /** + * Minimum corner of AABB in world space + * @attribute + */ + aabbMin = new Vec3(-0.5, -0.5, -0.5); + + /** + * Maximum corner of AABB in world space + * @attribute + */ + aabbMax = new Vec3(0.5, 0.5, 0.5); + + /** + * Scale factor for edge splats (lower = more aggressive scaling) + * @attribute + */ + edgeScaleFactor = 0.5; + + getShaderGLSL() { + return shaderGLSL; + } + + getShaderWGSL() { + return shaderWGSL; + } + + updateEffect(effectTime, dt) { + // Set AABB uniforms + this._aabbMinArray[0] = this.aabbMin.x; + this._aabbMinArray[1] = this.aabbMin.y; + this._aabbMinArray[2] = this.aabbMin.z; + this.setUniform('uAabbMin', this._aabbMinArray); + + this._aabbMaxArray[0] = this.aabbMax.x; + this._aabbMaxArray[1] = this.aabbMax.y; + this._aabbMaxArray[2] = this.aabbMax.z; + this.setUniform('uAabbMax', this._aabbMaxArray); + + // Set edge scale factor + this.setUniform('uEdgeScaleFactor', this.edgeScaleFactor); + } +} + +export { GsplatCropShaderEffect }; diff --git a/scripts/esm/gsplat/streamed-gsplat.mjs b/scripts/esm/gsplat/streamed-gsplat.mjs new file mode 100644 index 00000000000..27a3b61a233 --- /dev/null +++ b/scripts/esm/gsplat/streamed-gsplat.mjs @@ -0,0 +1,329 @@ +import { Script, Asset, Entity, platform } from 'playcanvas'; + +class StreamedGsplat extends Script { + static scriptName = 'streamedGsplat'; + + /** + * @attribute + * @type {string} + */ + splatUrl = ''; + + /** + * @attribute + * @type {string} + */ + environmentUrl = ''; + + /** + * @attribute + * @type {number[]} + */ + ultraLodDistances = [5, 20, 35, 50, 65, 90, 150]; + + /** + * @attribute + * @type {number[]} + */ + highLodDistances = [5, 20, 35, 50, 65, 90, 150]; + + /** + * @attribute + * @type {number[]} + */ + mediumLodDistances = [5, 7, 12, 25, 75, 120, 200]; + + /** + * @attribute + * @type {number[]} + */ + lowLodDistances = [5, 7, 12, 25, 75, 120, 200]; + + /** + * @attribute + * @type {number[]} + */ + ultraLodRange = [0, 5]; + + /** + * @attribute + * @type {number[]} + */ + highLodRange = [1, 5]; + + /** + * @attribute + * @type {number[]} + */ + mediumLodRange = [2, 5]; + + /** + * @attribute + * @type {number[]} + */ + lowLodRange = [3, 5]; + + /** + * @attribute + * @type {number} + */ + ultraSplatBudget = 6000000; + + /** + * @attribute + * @type {number} + */ + highSplatBudget = 4000000; + + /** + * @attribute + * @type {number} + */ + mediumSplatBudget = 2000000; + + /** + * @attribute + * @type {number} + */ + lowSplatBudget = 1000000; + + /** @type {Asset[]} */ + _assets = []; + + /** @type {Entity[]} */ + _children = []; + + _highRes = false; + + _colorize = false; + + initialize() { + const app = this.app; + + this._currentPreset = platform.mobile ? 'low' : 'medium'; + + // global settings + app.scene.gsplat.radialSorting = true; + app.scene.gsplat.lodUpdateAngle = 90; + app.scene.gsplat.lodBehindPenalty = 5; + app.scene.gsplat.lodUpdateDistance = 1; + app.scene.gsplat.lodUnderfillLimit = 10; + + // Listen for UI events + app.on('preset:ultra', () => this._setPreset('ultra'), this); + app.on('preset:high', () => this._setPreset('high'), this); + app.on('preset:medium', () => this._setPreset('medium'), this); + app.on('preset:low', () => this._setPreset('low'), this); + app.on('colorize:toggle', this._toggleColorize, this); + + // Apply initial resolution + this._applyResolution(); + + // Load main splat - attach to entity directly + if (!this.splatUrl) { + console.warn('[StreamedGsplat] No splatUrl provided.'); + } else { + const mainAsset = new Asset('MainGsplat_asset', 'gsplat', { url: this.splatUrl }); + app.assets.add(mainAsset); + app.assets.load(mainAsset); + this._assets.push(mainAsset); + + mainAsset.ready((a) => { + // Temporarily disable entity to allow unified property to be set + const wasEnabled = this.entity.enabled; + this.entity.enabled = false; + + // Add component directly to this entity + this.entity.addComponent('gsplat', { + unified: true, + lodDistances: this._getCurrentLodDistances(), + asset: a + }); + + // Restore entity enabled state + this.entity.enabled = wasEnabled; + + // Apply initial preset + this._applyPreset(); + }); + } + + // Load environment splat - attach to child entity + if (!this.environmentUrl) { + console.warn('[StreamedGsplat] No environmentUrl provided (skipping env child).'); + } else { + const envAsset = new Asset('EnvironmentGsplat_asset', 'gsplat', { url: this.environmentUrl }); + app.assets.add(envAsset); + app.assets.load(envAsset); + this._assets.push(envAsset); + + envAsset.ready((a) => { + // Create child entity disabled to allow unified property to be set + const child = new Entity('EnvironmentGsplat'); + child.enabled = false; + + // Attach to the scene graph + this.entity.addChild(child); + this._children.push(child); + + // Add the component while entity is disabled + child.addComponent('gsplat', { + unified: true, + lodDistances: this._getCurrentLodDistances(), + asset: a + }); + + // Enable the child entity + child.enabled = true; + }); + } + + this.once('destroy', () => { + this.onDestroy(); + }); + } + + _getCurrentLodDistances() { + let distances; + switch (this._currentPreset) { + case 'ultra': + distances = this.ultraLodDistances; + break; + case 'high': + distances = this.highLodDistances; + break; + case 'medium': + distances = this.mediumLodDistances; + break; + case 'low': + distances = this.lowLodDistances; + break; + default: + distances = [5, 20, 35, 50, 65, 90, 150]; + } + return distances && distances.length > 0 ? distances : [5, 20, 35, 50, 65, 90, 150]; + } + + _getCurrentLodRange() { + let range; + switch (this._currentPreset) { + case 'ultra': + range = this.ultraLodRange; + break; + case 'high': + range = this.highLodRange; + break; + case 'medium': + range = this.mediumLodRange; + break; + case 'low': + range = this.lowLodRange; + break; + default: + range = [0, 5]; + } + return range && range.length >= 2 ? range : [0, 5]; + } + + _getCurrentSplatBudget() { + let budget; + switch (this._currentPreset) { + case 'ultra': + budget = this.ultraSplatBudget; + break; + case 'high': + budget = this.highSplatBudget; + break; + case 'medium': + budget = this.mediumSplatBudget; + break; + case 'low': + budget = this.lowSplatBudget; + break; + default: + budget = 0; + } + return budget || 0; + } + + _applyPreset() { + const range = this._getCurrentLodRange(); + if (!range) return; + + const app = this.app; + app.scene.gsplat.lodRangeMin = range[0]; + app.scene.gsplat.lodRangeMax = range[1]; + + const lodDistances = this._getCurrentLodDistances(); + const splatBudget = this._getCurrentSplatBudget(); + + // Apply to main streaming asset only (environment doesn't support these settings) + if (this.entity.gsplat) { + this.entity.gsplat.lodDistances = lodDistances; + this.entity.gsplat.splatBudget = splatBudget; + } + } + + _setPreset(presetName) { + this._currentPreset = presetName; + this._applyPreset(); + + // Notify UI of preset change + this.app.fire('ui:setPreset', presetName); + } + + _applyResolution() { + const device = this.app.graphicsDevice; + const dpr = window.devicePixelRatio || 1; + device.maxPixelRatio = this._highRes ? Math.min(dpr, 2) : (dpr >= 2 ? dpr * 0.5 : dpr); + this.app.resizeCanvas(); + } + + _toggleColorize() { + this._colorize = !this._colorize; + this.app.scene.gsplat.colorizeLod = this._colorize; + + const statusEl = document.getElementById('colorize-status'); + if (statusEl) { + statusEl.textContent = this._colorize ? 'On' : 'Off'; + } + } + + update() { + const rendered = this.app.stats.frame.gsplats || 0; + this.app.fire('ui:updateStats', rendered); + } + + onDestroy() { + // Clean up event listeners + this.app.off('preset:ultra'); + this.app.off('preset:high'); + this.app.off('preset:medium'); + this.app.off('preset:low'); + this.app.off('colorize:toggle'); + + // unload/remove assets + for (let i = 0; i < this._assets.length; i++) { + const a = this._assets[i]; + if (a) { + a.unload(); + this.app.assets.remove(a); + } + } + this._assets.length = 0; + + // remove gsplat component from entity if present + if (this.entity.gsplat) { + this.entity.removeComponent('gsplat'); + } + + // destroy created children + for (let j = 0; j < this._children.length; j++) { + const c = this._children[j]; + if (c && c.destroy) c.destroy(); + } + this._children.length = 0; + } +} + +export { StreamedGsplat }; diff --git a/scripts/esm/shadow-catcher.mjs b/scripts/esm/shadow-catcher.mjs index 664027730aa..06b63a0d0b8 100644 --- a/scripts/esm/shadow-catcher.mjs +++ b/scripts/esm/shadow-catcher.mjs @@ -85,7 +85,8 @@ class ShadowCatcher extends Script { this.geometry?.render?.meshInstances.forEach((mi) => { // set up the geometry to render very early during the transparent pass, before other transparent objects - mi.drawOrder = -1; + // use drawBucket for coarse sorting - higher bucket renders first in back-to-front mode + mi.drawBucket = 250; // if geometry was provided, set the material if (!this._geometryCreated) { diff --git a/src/core/constants.js b/src/core/constants.js index 30f01bd10ec..d54ab54487c 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -139,6 +139,13 @@ export const TRACEID_ELEMENT = 'Element'; */ export const TRACEID_TEXTURES = 'Textures'; +/** + * Logs all assets in the asset registry. + * + * @category Debug + */ +export const TRACEID_ASSETS = 'Assets'; + /** * Logs the render queue commands. * diff --git a/src/core/set-utils.js b/src/core/set-utils.js new file mode 100644 index 00000000000..0b06ad4cd35 --- /dev/null +++ b/src/core/set-utils.js @@ -0,0 +1,32 @@ +/** + * Set utility functions. + * + * @ignore + */ +class SetUtils { + /** + * Compares two sets for equality. Returns true if both sets have the same size and contain + * the same elements. + * + * @param {Set} setA - First set to compare. + * @param {Set} setB - Second set to compare. + * @returns {boolean} True if sets are equal, false otherwise. + */ + static equals(setA, setB) { + // Quick size check + if (setA.size !== setB.size) { + return false; + } + + // Check if all elements in setA are in setB + for (const elem of setA) { + if (!setB.has(elem)) { + return false; + } + } + + return true; + } +} + +export { SetUtils }; diff --git a/src/core/shape/bounding-box.js b/src/core/shape/bounding-box.js index e2274f41267..af81222bd92 100644 --- a/src/core/shape/bounding-box.js +++ b/src/core/shape/bounding-box.js @@ -290,18 +290,46 @@ class BoundingBox { * @returns {boolean} True if the point is inside the AABB and false otherwise. */ containsPoint(point) { - const min = this.getMin(); - const max = this.getMax(); + const c = this.center; + const h = this.halfExtents; - if (point.x < min.x || point.x > max.x || - point.y < min.y || point.y > max.y || - point.z < min.z || point.z > max.z) { + if (point.x < c.x - h.x || point.x > c.x + h.x || + point.y < c.y - h.y || point.y > c.y + h.y || + point.z < c.z - h.z || point.z > c.z + h.z) { return false; } return true; } + /** + * Return the point on the AABB closest to a given point. If the point is inside the AABB, the + * point itself is returned. + * + * @param {Vec3} point - Point to find the closest point to. + * @param {Vec3} [result] - The vector to store the result in. If not provided, a new Vec3 is + * created and returned. + * @returns {Vec3} The closest point on the AABB. + * @example + * const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + * const point = new Vec3(2, 0, 0); + * const closest = box.closestPoint(point); // Returns Vec3(1, 0, 0) + * @example + * // Reuse a result vector to avoid allocations in hot paths + * const result = new Vec3(); + * box.closestPoint(point, result); + */ + closestPoint(point, result = new Vec3()) { + const c = this.center; + const h = this.halfExtents; + + return result.set( + Math.max(c.x - h.x, Math.min(point.x, c.x + h.x)), + Math.max(c.y - h.y, Math.min(point.y, c.y + h.y)), + Math.max(c.z - h.z, Math.min(point.z, c.z + h.z)) + ); + } + /** * Set an AABB to enclose the specified AABB if it were to be transformed by the specified 4x4 * matrix. diff --git a/src/core/tracing.js b/src/core/tracing.js index a363b9c7f3a..621d2417652 100644 --- a/src/core/tracing.js +++ b/src/core/tracing.js @@ -42,6 +42,7 @@ class Tracing { * - {@link TRACEID_COMPUTEPIPELINE_ALLOC} * - {@link TRACEID_PIPELINELAYOUT_ALLOC} * - {@link TRACEID_TEXTURES} + * - {@link TRACEID_ASSETS} * - {@link TRACEID_GPU_TIMINGS} * * @param {boolean} enabled - New enabled state for the channel. diff --git a/src/extras/exporters/usdz-exporter.js b/src/extras/exporters/usdz-exporter.js index 6cc75da123c..7e881e6d451 100644 --- a/src/extras/exporters/usdz-exporter.js +++ b/src/extras/exporters/usdz-exporter.js @@ -438,8 +438,14 @@ class UsdzExporter extends CoreExporter { addTexture('normalMap', null, 'normal3f', 'normal', 'normal'); addTexture('emissiveMap', material.emissive, 'color3f', 'emissiveColor', 'emissive', false, true); addTexture('aoMap', null, 'float', 'occlusion', 'occlusion'); - addTexture('metalnessMap', material.metalness, 'float', 'metallic', 'metallic'); - addTexture('glossMap', material.gloss, 'float', 'roughness', 'roughness'); + + // only export metalness for metalness workflow materials + const metalness = material.useMetalness ? material.metalness : 0; + addTexture('metalnessMap', metalness, 'float', 'metallic', 'metallic'); + + // convert gloss to roughness (they are inverse of each other) + const roughness = material.glossInvert ? material.gloss : 1 - material.gloss; + addTexture('glossMap', roughness, 'float', 'roughness', 'roughness'); // main material object const materialObject = ` diff --git a/src/extras/gizmo/gizmo.js b/src/extras/gizmo/gizmo.js index f14eafe6edf..9ff85542d7a 100644 --- a/src/extras/gizmo/gizmo.js +++ b/src/extras/gizmo/gizmo.js @@ -197,6 +197,14 @@ class Gizmo extends EventHandler { */ _handles = []; + /** + * Internal array of mouse buttons that can interact with the gizmo. + * + * @type {[boolean, boolean, boolean]} + * @protected + */ + _mouseButtons = [true, true, true]; + /** * Internal reference to camera component to view the gizmo. * @@ -331,6 +339,22 @@ class Gizmo extends EventHandler { return this.root.enabled; } + /** + * Array of mouse buttons that can interact with the gizmo. The button indices are defined as: + * + * - 0: Left button + * - 1: Middle button + * - 2: Right button + * + * The full list of button indices can be found here: + * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} + * + * @type {[boolean, boolean, boolean]} + */ + get mouseButtons() { + return this._mouseButtons; + } + /** * Sets the gizmo render layer. * @@ -450,6 +474,9 @@ class Gizmo extends EventHandler { if (!this.enabled || document.pointerLockElement) { return; } + if (!this.mouseButtons[e.button]) { + return; + } const selection = this._getSelection(e.offsetX, e.offsetY); if (selection[0]) { if (this.preventDefault) { @@ -491,6 +518,9 @@ class Gizmo extends EventHandler { if (!this.enabled || document.pointerLockElement) { return; } + if (!this.mouseButtons[e.button]) { + return; + } const selection = this._getSelection(e.offsetX, e.offsetY); if (selection[0]) { if (this.preventDefault) { diff --git a/src/extras/gizmo/transform-gizmo.js b/src/extras/gizmo/transform-gizmo.js index b0547229ee7..3b4a9c7685f 100644 --- a/src/extras/gizmo/transform-gizmo.js +++ b/src/extras/gizmo/transform-gizmo.js @@ -197,12 +197,11 @@ class TransformGizmo extends Gizmo { _selectionStartPoint = new Vec3(); /** - * Internal state for if snapping is enabled. Defaults to false. + * Whether snapping is enabled. Defaults to false. * * @type {boolean} - * @private */ - _snap = false; + snap = false; /** * Snapping increment. Defaults to 1. @@ -293,7 +292,6 @@ class TransformGizmo extends Gizmo { }); this.on(Gizmo.EVENT_NODESDETACH, () => { - this.snap = false; this._hoverAxis = ''; this._hoverIsPlane = false; this._hover(); @@ -301,24 +299,6 @@ class TransformGizmo extends Gizmo { }); } - /** - * Sets whether snapping is enabled. Defaults to false. - * - * @type {boolean} - */ - set snap(value) { - this._snap = this.enabled && value; - } - - /** - * Gets whether snapping is enabled. Defaults to false. - * - * @type {boolean} - */ - get snap() { - return this._snap; - } - /** * Gets the current theme for the gizmo. * diff --git a/src/extras/mini-stats/graph.js b/src/extras/mini-stats/graph.js index 890bc9bf44e..571294b9070 100644 --- a/src/extras/mini-stats/graph.js +++ b/src/extras/mini-stats/graph.js @@ -12,13 +12,17 @@ class Graph { this.avgTotal = 0; this.avgTimer = 0; this.avgCount = 0; + this.maxValue = 0; this.timingText = ''; + this.maxText = ''; this.texture = null; this.yOffset = 0; + this.graphType = 0.0; this.cursor = 0; this.sample = new Uint8ClampedArray(4); this.sample.set([0, 0, 0, 255]); + this.needsClear = false; this.counter = 0; @@ -43,16 +47,19 @@ class Graph { // calculate stacked total const total = timings.reduce((a, v) => a + v, 0); - // update averages + // update averages and max this.avgTotal += total; this.avgTimer += ms; this.avgCount++; + this.maxValue = Math.max(this.maxValue, total); if (this.avgTimer > this.textRefreshRate) { this.timingText = (this.avgTotal / this.avgCount).toFixed(this.timer.decimalPlaces); + this.maxText = this.maxValue.toFixed(this.timer.decimalPlaces); this.avgTimer = 0; this.avgTotal = 0; this.avgCount = 0; + this.maxValue = 0; } if (this.enabled) { @@ -68,8 +75,21 @@ class Graph { // .a store watermark this.sample[3] = this.watermark / range * 255; + // bounds check - skip if texture is too small + if (this.yOffset >= this.texture.height) { + return; + } + // write latest sample const data = this.texture.lock(); + + // clear entire row if needed (when row is newly allocated) + if (this.needsClear) { + const rowOffset = this.yOffset * this.texture.width * 4; + data.fill(0, rowOffset, rowOffset + this.texture.width * 4); + this.needsClear = false; + } + data.set(this.sample, (this.cursor + this.yOffset * this.texture.width) * 4); this.texture.unlock(); @@ -87,7 +107,7 @@ class Graph { this.enabled ? 0.5 + this.yOffset : this.texture.height - 1, -w, 0, this.texture, - 0); + this.graphType); } } diff --git a/src/extras/mini-stats/mini-stats.js b/src/extras/mini-stats/mini-stats.js index 8aaff54756e..fbfe25a32f9 100644 --- a/src/extras/mini-stats/mini-stats.js +++ b/src/extras/mini-stats/mini-stats.js @@ -14,6 +14,21 @@ import { Render2d } from './render2d.js'; * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' */ +// CPU stat name mappings: full property name -> shortened display name +const cpuStatDisplayNames = { + animUpdate: 'anim', + physicsTime: 'physics', + renderTime: 'render', + gsplatSort: 'gsplatSort' +}; + +// CPU stats with delayed creation (only shown once non-zero, but never removed) +const delayedStartStats = new Set([ + 'physicsTime', + 'animUpdate', + 'gsplatSort' +]); + /** * @typedef {object} MiniStatsSizeOptions * @property {number} width - Width of the graph area. @@ -49,6 +64,10 @@ import { Render2d } from './render2d.js'; * @property {MiniStatsProcessorOptions} gpu - GPU graph options. * @property {MiniStatsGraphOptions[]} stats - Array of options to render additional graphs based * on stats collected into Application.stats. + * @property {number} [gpuTimingMinSize] - Minimum size index at which to show GPU pass timing + * graphs. Defaults to 1. + * @property {number} [cpuTimingMinSize] - Minimum size index at which to show CPU sub-timing + * graphs (script, anim, physics, render). Defaults to 1. */ /** @@ -66,26 +85,49 @@ class MiniStats { * // create a new MiniStats instance using default options * const miniStats = new pc.MiniStats(app); */ - constructor(app, options) { + constructor(app, options = MiniStats.getDefaultOptions()) { const device = app.graphicsDevice; - options = options || MiniStats.getDefaultOptions(); + // Persistent texture row allocation (must be initialized before initGraphs) + this.graphRows = new Map(); // Map + this.freeRows = []; // Available rows for reuse + this.nextRowIndex = 0; // Next new row to allocate + + // sizes must be set before initGraphs (needed by ensureTextureHeight) + this.sizes = options.sizes; // create graphs this.initGraphs(app, device, options); // extract list of words const words = new Set( - ['', 'ms', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-'] + ['', 'ms', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', ' '] .concat(this.graphs.map(graph => graph.name)) .concat(options.stats ? options.stats.map(stat => stat.unitsName) : []) .filter(item => !!item) ); + // always add lowercase and uppercase letters (needed for "max" display and GPU pass names) + for (let i = 97; i <= 122; i++) { + words.add(String.fromCharCode(i)); + } + for (let i = 65; i <= 90; i++) { + words.add(String.fromCharCode(i)); + } + this.wordAtlas = new WordAtlas(device, words); - this.sizes = options.sizes; this._activeSizeIndex = options.startSizeIndex; + // if GPU pass tracking or CPU timing is enabled, use the last width for medium/large sizes + const gpuTimingMinSize = options.gpuTimingMinSize ?? 1; + const cpuTimingMinSize = options.cpuTimingMinSize ?? 1; + if (gpuTimingMinSize < this.sizes.length || cpuTimingMinSize < this.sizes.length) { + const lastWidth = this.sizes[this.sizes.length - 1].width; + for (let i = 1; i < this.sizes.length - 1; i++) { + this.sizes[i].width = lastWidth; + } + } + // create click region so we can resize const div = document.createElement('div'); div.setAttribute('id', 'mini-stats'); @@ -97,7 +139,8 @@ class MiniStats { }); div.addEventListener('mouseleave', (event) => { - this.opacity = 0.7; + // larger sizes have higher default opacity + this.opacity = this._activeSizeIndex > 0 ? 0.85 : 0.7; }); div.addEventListener('click', (event) => { @@ -121,10 +164,22 @@ class MiniStats { this.width = 0; this.height = 0; this.gspacing = 2; - this.clr = [1, 1, 1, 0.5]; + // initial opacity depends on starting size + this.clr = [1, 1, 1, options.startSizeIndex > 0 ? 0.85 : 0.7]; this._enabled = true; + // GPU pass tracking + this.gpuTimingMinSize = gpuTimingMinSize; + this.gpuPassGraphs = new Map(); // Map + + // CPU sub-timing tracking + this.cpuTimingMinSize = cpuTimingMinSize; + this.cpuGraphs = new Map(); // Map + + this.frameIndex = 0; + this.textRefreshRate = options.textRefreshRate; + // initial resize this.activeSizeIndex = this._activeSizeIndex; } @@ -141,6 +196,7 @@ class MiniStats { this.app.off('postrender', this.postRender, this); this.graphs.forEach(graph => graph.destroy()); + this.gpuPassGraphs.clear(); this.wordAtlas.destroy(); this.texture.destroy(); this.div.remove(); @@ -212,7 +268,13 @@ class MiniStats { stats: ['drawCalls.total'], watermark: 1000 } - ] + ], + + // minimum size index to show GPU pass timing graphs + gpuTimingMinSize: 1, + + // minimum size index to show CPU sub-timing graphs + cpuTimingMinSize: 1 }; } @@ -226,7 +288,45 @@ class MiniStats { set activeSizeIndex(value) { this._activeSizeIndex = value; this.gspacing = this.sizes[value].spacing; + this.resize(this.sizes[value].width, this.sizes[value].height, this.sizes[value].graphs); + + // update opacity based on size (larger sizes have higher default opacity) + this.opacity = value > 0 ? 0.85 : 0.7; + + // delete GPU pass graphs when switching below threshold + if (value < this.gpuTimingMinSize && this.gpuPassGraphs) { + for (const passData of this.gpuPassGraphs.values()) { + const index = this.graphs.indexOf(passData.graph); + if (index !== -1) { + this.graphs.splice(index, 1); + } + this.freeRow(passData.graph); + passData.graph.destroy(); + } + this.gpuPassGraphs.clear(); + + // reset main GPU graph to default background color + const gpuGraph = this.graphs.find(g => g.name === 'GPU'); + if (gpuGraph) gpuGraph.graphType = 0.0; + } + + // delete CPU sub-timing graphs when switching below threshold + if (value < this.cpuTimingMinSize && this.cpuGraphs) { + for (const statData of this.cpuGraphs.values()) { + const index = this.graphs.indexOf(statData.graph); + if (index !== -1) { + this.graphs.splice(index, 1); + } + this.freeRow(statData.graph); + statData.graph.destroy(); + } + this.cpuGraphs.clear(); + + // reset main CPU graph to default background color + const cpuGraph = this.graphs.find(g => g.name === 'CPU'); + if (cpuGraph) cpuGraph.graphType = 0.0; + } } /** @@ -295,6 +395,7 @@ class MiniStats { return this._enabled; } + /** * Create the graphs requested by the user and add them to the MiniStats instance. * @@ -326,14 +427,10 @@ class MiniStats { }); } - const maxWidth = options.sizes.reduce((max, v) => { - return v.width > max ? v.width : max; - }, 0); - this.texture = new Texture(device, { name: 'mini-stats-graph-texture', - width: math.nextPowerOfTwo(maxWidth), - height: math.nextPowerOfTwo(this.graphs.length), + width: 1, + height: 1, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, @@ -341,9 +438,9 @@ class MiniStats { addressV: ADDRESS_REPEAT }); - this.graphs.forEach((graph, i) => { + this.graphs.forEach((graph) => { graph.texture = this.texture; - graph.yOffset = i; + this.allocateRow(graph); }); } @@ -378,16 +475,27 @@ class MiniStats { // name + space x += wordAtlas.render(render2d, graph.name, x, y) + 10; - // timing + // timing (average value) const timingText = graph.timingText; for (let j = 0; j < timingText.length; ++j) { x += wordAtlas.render(render2d, timingText[j], x, y); } - // units + // max value (only on larger sizes) + if (graph.maxText && this._activeSizeIndex > 0) { + x += 5; + x += wordAtlas.render(render2d, 'max', x, y); + x += 5; + + const maxText = graph.maxText; + for (let j = 0; j < maxText.length; ++j) { + x += wordAtlas.render(render2d, maxText[j], x, y); + } + } + + // units (at the end, after both average and max) if (graph.timer.unitsName) { - x += 3; - wordAtlas.render(render2d, graph.timer.unitsName, x, y); + x += wordAtlas.render(render2d, graph.timer.unitsName, x, y); } } @@ -437,6 +545,189 @@ class MiniStats { this.graphs.forEach(graph => graph.loseContext()); } + /** + * Update sub-stat graphs (GPU passes or CPU timings). + * @param {Map} subGraphs - Map to store graph data (gpuPassGraphs or cpuGraphs) + * @param {string} mainGraphName - Name of main graph ('GPU' or 'CPU') + * @param {Map|Object} stats - Stats data (Map for GPU, object for CPU) + * @param {string} statPathPrefix - Prefix for stat path ('gpu' for GPU, 'frame' for CPU) + * @param {number} removeAfterFrames - Frames of zero before removal + * @private + */ + updateSubStats(subGraphs, mainGraphName, stats, statPathPrefix, removeAfterFrames) { + const passesToRemove = []; + + // check existing sub-stats for removal + for (const [statName, statData] of subGraphs) { + const timing = (stats instanceof Map) ? (stats.get(statName) || 0) : (stats[statName] || 0); + + if (timing > 0) { + // update last non-zero frame + statData.lastNonZeroFrame = this.frameIndex; + } else if (removeAfterFrames > 0) { + // Only GPU passes auto-hide; CPU stats are never removed + const shouldAutoHide = statPathPrefix === 'gpu'; + if (shouldAutoHide && this.frameIndex - statData.lastNonZeroFrame > removeAfterFrames) { + passesToRemove.push(statName); + } + } + } + + // remove stats that have been zero for too long + for (const statName of passesToRemove) { + const statData = subGraphs.get(statName); + if (statData) { + // remove from graphs array + const index = this.graphs.indexOf(statData.graph); + if (index !== -1) { + this.graphs.splice(index, 1); + } + this.freeRow(statData.graph); + statData.graph.destroy(); + subGraphs.delete(statName); + } + } + + // scan for new sub-stats + const statsEntries = (stats instanceof Map) ? stats : Object.entries(stats); + for (const [statName, timing] of statsEntries) { + if (!subGraphs.has(statName)) { + // Skip creating graph for auto-hide stats with zero timing + // Skip creating graph for GPU passes or delayed-start CPU stats with zero timing + const isDelayedStart = statPathPrefix === 'gpu' || delayedStartStats.has(statName); + if (isDelayedStart && timing === 0) { + continue; + } + + // create new graph for this stat + // shorten display name for CPU stats + let displayName = statName; + if (statPathPrefix === 'frame') { + displayName = cpuStatDisplayNames[statName] || statName; + } + const graphName = ` ${displayName}`; // indent with 2 spaces + + // initial watermark (will be synced to main graph) + const watermark = 10.0; + + const statPath = `${statPathPrefix}.${statName}`; + const timer = new StatsTimer(this.app, [statPath], 1, 'ms', 1); + const graph = new Graph(graphName, this.app, watermark, this.textRefreshRate, timer); + + // Set graph type for background tinting + if (statPathPrefix === 'gpu') { + graph.graphType = 0.33; // GPU sub-graphs + } else if (statPathPrefix === 'frame') { + graph.graphType = 0.66; // CPU sub-graphs + } + + graph.texture = this.texture; + this.allocateRow(graph); + + // match the current display mode + const currentSize = this.sizes[this._activeSizeIndex]; + graph.enabled = currentSize.graphs; + + // find the main graph index and insert before it (graphs render bottom to top) + let mainGraphIndex = this.graphs.findIndex(g => g.name === mainGraphName); + if (mainGraphIndex === -1) { + mainGraphIndex = 0; // fallback to start if main graph not found + } + + // find where to insert - right before the main graph, after any existing sub-stats + let insertIndex = mainGraphIndex; + for (let i = mainGraphIndex - 1; i >= 0; i--) { + // check if this is an indented sub-stat (starts with spaces) + if (this.graphs[i].name.startsWith(' ')) { + insertIndex = i; + } else { + break; + } + } + + // insert the new graph at the correct position + this.graphs.splice(insertIndex, 0, graph); + + subGraphs.set(statName, { + graph: graph, + lastNonZeroFrame: timing > 0 ? this.frameIndex : this.frameIndex - removeAfterFrames - 1 + }); + } + } + + // sync all sub-stat watermarks to match main graph + const mainGraph = this.graphs.find(g => g.name === mainGraphName); + if (mainGraph) { + for (const statData of subGraphs.values()) { + statData.graph.watermark = mainGraph.watermark; + } + + // set main graph background color to match sub-graphs when they exist + if (subGraphs.size > 0) { + if (statPathPrefix === 'gpu') { + mainGraph.graphType = 0.33; // Match GPU sub-graphs + } else if (statPathPrefix === 'frame') { + mainGraph.graphType = 0.66; // Match CPU sub-graphs + } + } else { + // reset to default background when no sub-graphs + mainGraph.graphType = 0.0; + } + } + } + + /** + * Allocates a texture row for a graph. Reuses free rows when available. + * + * @param {Graph} graph - The graph to allocate a row for. + * @returns {number} The allocated row index. + * @private + */ + allocateRow(graph) { + let row; + if (this.freeRows.length > 0) { + row = this.freeRows.pop(); + } else { + row = this.nextRowIndex++; + this.ensureTextureHeight(this.nextRowIndex); + } + this.graphRows.set(graph, row); + graph.yOffset = row; + graph.needsClear = true; // Will clear on first update() + return row; + } + + /** + * Frees a texture row when a graph is destroyed. + * + * @param {Graph} graph - The graph whose row to free. + * @private + */ + freeRow(graph) { + const row = this.graphRows.get(graph); + if (row !== undefined) { + this.freeRows.push(row); + this.graphRows.delete(graph); + } + } + + /** + * Ensures the texture has enough rows. Only grows, never shrinks. + * + * @param {number} requiredRows - The minimum number of rows needed. + * @private + */ + ensureTextureHeight(requiredRows) { + const maxWidth = this.sizes[this.sizes.length - 1].width; + const requiredWidth = math.nextPowerOfTwo(maxWidth); + const requiredHeight = math.nextPowerOfTwo(requiredRows); + + // Only grow, never shrink + if (requiredHeight > this.texture.height) { + this.texture.resize(requiredWidth, requiredHeight); + } + } + /** * Called when the `postrender` event is fired by the application. * @@ -445,7 +736,30 @@ class MiniStats { postRender() { if (this._enabled) { this.render(); + + // Update GPU pass graphs when size index meets threshold + if (this._activeSizeIndex >= this.gpuTimingMinSize) { + const gpuStats = this.app.stats.gpu; + if (gpuStats) { + this.updateSubStats(this.gpuPassGraphs, 'GPU', gpuStats, 'gpu', 240); + } + } + + // Update CPU sub-timing graphs when size index meets threshold + if (this._activeSizeIndex >= this.cpuTimingMinSize) { + const cpuStats = { + scriptUpdate: this.app.stats.frame.scriptUpdate, + scriptPostUpdate: this.app.stats.frame.scriptPostUpdate, + animUpdate: this.app.stats.frame.animUpdate, + physicsTime: this.app.stats.frame.physicsTime, + renderTime: this.app.stats.frame.renderTime, + gsplatSort: this.app.stats.frame.gsplatSort + }; + this.updateSubStats(this.cpuGraphs, 'CPU', cpuStats, 'frame', 240); + } } + + this.frameIndex++; } } diff --git a/src/extras/mini-stats/render2d.js b/src/extras/mini-stats/render2d.js index 7c7494a02e0..740c6d3ca35 100644 --- a/src/extras/mini-stats/render2d.js +++ b/src/extras/mini-stats/render2d.js @@ -9,6 +9,7 @@ import { SEMANTIC_TEXCOORD0, TYPE_FLOAT32 } from '../../platform/graphics/constants.js'; +import { Debug } from '../../core/debug.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { GraphNode } from '../../scene/graph-node.js'; @@ -19,6 +20,16 @@ import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js'; import { VertexFormat } from '../../platform/graphics/vertex-format.js'; import { ShaderMaterial } from '../../scene/materials/shader-material.js'; +// Graph colors for MiniStats +const graphColorRed = '1.0, 0.412, 0.380'; // Pastel Red +const graphColorGreen = '0.467, 0.867, 0.467'; // Pastel Green +const graphColorBlue = '0.424, 0.627, 0.863'; // Little Boy Blue + +// Background colors for MiniStats graphs +const mainBackgroundColor = '0.0, 0.0, 0.0'; +const gpuBackgroundColor = '0.15, 0.15, 0.0'; +const cpuBackgroundColor = '0.15, 0.0, 0.1'; + const vertexShaderGLSL = /* glsl */ ` attribute vec3 vertex_position; // unnormalized xy, word flag attribute vec4 vertex_texCoord0; // unnormalized texture space uv, normalized uv @@ -66,17 +77,29 @@ const fragmentShaderGLSL = /* glsl */ ` vec4 graph; if (uv0.w < graphSample.r) - graph = vec4(0.7, 0.2, 0.2, 1.0); + graph = vec4(${graphColorRed}, 1.0); else if (uv0.w < graphSample.g) - graph = vec4(0.2, 0.7, 0.2, 1.0); + graph = vec4(${graphColorGreen}, 1.0); else if (uv0.w < graphSample.b) - graph = vec4(0.2, 0.2, 0.7, 1.0); - else - graph = vec4(0.0, 0.0, 0.0, 1.0 - 0.25 * sin(uv0.w * 3.14159)); + graph = vec4(${graphColorBlue}, 1.0); + else { + vec3 bgColor = vec3(${mainBackgroundColor}); + if (wordFlag > 0.5) { + bgColor = vec3(${cpuBackgroundColor}); // CPU: red tint + } else if (wordFlag > 0.2) { + bgColor = vec3(${gpuBackgroundColor}); // GPU: blue tint + } + graph = vec4(bgColor, 1.0); + } vec4 words = texture2D(wordsTex, vec2(uv0.x, 1.0 - uv0.y)); - gl_FragColor = mix(graph, words, wordFlag) * clr; + // Binary blend: either graph or text, no partial mixing + if (wordFlag > 0.99) { + gl_FragColor = words * clr; + } else { + gl_FragColor = graph * clr; + } } `; @@ -98,26 +121,37 @@ const fragmentShaderWGSL = /* wgsl */ ` var graph: vec4f; if (uv0.w < graphSample.r) { - graph = vec4f(0.7, 0.2, 0.2, 1.0); + graph = vec4f(${graphColorRed}, 1.0); } else if (uv0.w < graphSample.g) { - graph = vec4f(0.2, 0.7, 0.2, 1.0); + graph = vec4f(${graphColorGreen}, 1.0); } else if (uv0.w < graphSample.b) { - graph = vec4f(0.2, 0.2, 0.7, 1.0); + graph = vec4f(${graphColorBlue}, 1.0); } else { - graph = vec4f(0.0, 0.0, 0.0, 1.0 - 0.25 * sin(uv0.w * 3.14159)); + var bgColor: vec3f = vec3f(${mainBackgroundColor}); + if (input.wordFlag > 0.5) { + bgColor = vec3f(${cpuBackgroundColor}); // CPU: red tint + } else if (input.wordFlag > 0.2) { + bgColor = vec3f(${gpuBackgroundColor}); // GPU: blue tint + } + graph = vec4f(bgColor, 1.0); } var words: vec4f = textureSample(wordsTex, wordsTex_sampler, vec2f(uv0.x, 1.0 - uv0.y)); var output: FragmentOutput; - output.color = mix(graph, words, input.wordFlag) * uniform.clr; + // Binary blend: either graph or text, no partial mixing + if (input.wordFlag > 0.99) { + output.color = words * uniform.clr; + } else { + output.color = graph * uniform.clr; + } return output; } `; // render 2d textured quads class Render2d { - constructor(device, maxQuads = 512) { + constructor(device, maxQuads = 2048) { const format = new VertexFormat(device, [ { semantic: SEMANTIC_POSITION, components: 3, type: TYPE_FLOAT32 }, { semantic: SEMANTIC_TEXCOORD0, components: 4, type: TYPE_FLOAT32 } @@ -135,6 +169,7 @@ class Render2d { } this.device = device; + this.maxQuads = maxQuads; this.buffer = new VertexBuffer(device, format, maxQuads * 4, { usage: BUFFER_STREAM }); @@ -185,6 +220,12 @@ class Render2d { } quad(x, y, w, h, u, v, uw, uh, texture, wordFlag = 0) { + // bounds check to prevent buffer overflow + if (this.quads >= this.maxQuads) { + Debug.warnOnce('MiniStats: maximum number of quads exceeded, some elements may not render.'); + return; + } + const rw = this.targetSize.width; const rh = this.targetSize.height; const x0 = x / rw; diff --git a/src/extras/mini-stats/stats-timer.js b/src/extras/mini-stats/stats-timer.js index 7927ade9959..b765ca3802f 100644 --- a/src/extras/mini-stats/stats-timer.js +++ b/src/extras/mini-stats/stats-timer.js @@ -17,7 +17,12 @@ class StatsTimer { // recursively look up properties of objects specified in a string const resolve = (path, obj) => { return path.split('.').reduce((prev, curr) => { - return prev ? prev[curr] : null; + if (!prev) return null; + // handle Map objects + if (prev instanceof Map) { + return prev.get(curr); + } + return prev[curr]; }, obj || this); }; @@ -25,7 +30,8 @@ class StatsTimer { for (let i = 0; i < this.statNames.length; i++) { // read specified stat from app.stats object - this.values[i] = resolve(this.statNames[i], this.app.stats) * this.multiplier; + const value = resolve(this.statNames[i], this.app.stats); + this.values[i] = (value ?? 0) * this.multiplier; } }); } diff --git a/src/extras/mini-stats/word-atlas.js b/src/extras/mini-stats/word-atlas.js index 03021a95b6e..e14c62b3a29 100644 --- a/src/extras/mini-stats/word-atlas.js +++ b/src/extras/mini-stats/word-atlas.js @@ -56,8 +56,8 @@ class WordAtlas { // render words placements.forEach((m, word) => { - // digits and '.' are white, the rest grey - context.fillStyle = isNumber(word) ? 'rgb(255, 255, 255)' : 'rgb(170, 170, 170)'; + // digits and '.' are yellow, the rest pastel cyan + context.fillStyle = isNumber(word) ? 'rgb(255, 240, 100)' : 'rgb(150, 220, 230)'; // render the word context.fillText(word, m.x - m.l, m.y + m.a); @@ -65,13 +65,13 @@ class WordAtlas { this.placements = placements; - // convert from black and white data to white texture with alpha + // preserve RGB color data and use max channel for alpha const data = context.getImageData(0, 0, canvas.width, canvas.height).data; for (let i = 0; i < data.length; i += 4) { - data[i + 3] = data[i + 0]; - data[i + 0] = 255; - data[i + 1] = 255; - data[i + 2] = 255; + // use max of RGB channels for alpha, multiply by 2 for bolder text + const maxChannel = Math.max(data[i + 0], data[i + 1], data[i + 2]); + data[i + 3] = Math.min(maxChannel * 2, 255); + // keep RGB as-is to preserve colors } this.texture = new Texture(device, { @@ -105,7 +105,34 @@ class WordAtlas { 1); return p.w; } - return 0; + + // if word not found, try rendering character by character + let totalWidth = 0; + for (let i = 0; i < word.length; i++) { + const char = word[i]; + + // handle spaces specially - they don't render but need width + if (char === ' ') { + totalWidth += 5; // fixed width for space + continue; + } + + const charPlacement = this.placements.get(char); + if (charPlacement) { + const padding = 1; + render2d.quad(x + totalWidth + charPlacement.l - padding, + y - charPlacement.d + padding, + charPlacement.w + padding * 2, + charPlacement.h + padding * 2, + charPlacement.x - padding, + this.texture.height - charPlacement.y - charPlacement.h - padding, + undefined, undefined, + this.texture, + 1); + totalWidth += charPlacement.w; + } + } + return totalWidth; } } diff --git a/src/extras/renderers/outline-renderer.js b/src/extras/renderers/outline-renderer.js index ea297fd3f48..41b0cb26bed 100644 --- a/src/extras/renderers/outline-renderer.js +++ b/src/extras/renderers/outline-renderer.js @@ -5,8 +5,7 @@ import { ADDRESS_CLAMP_TO_EDGE, BLENDEQUATION_ADD, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA, CULLFACE_NONE, FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, PIXELFORMAT_SRGBA8, - SEMANTIC_POSITION, - SHADERLANGUAGE_GLSL + SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; @@ -15,7 +14,6 @@ import { drawQuadWithShader } from '../../scene/graphics/quad-render-utils.js'; import { QuadRender } from '../../scene/graphics/quad-render.js'; import { StandardMaterialOptions } from '../../scene/materials/standard-material-options.js'; import { StandardMaterial } from '../../scene/materials/standard-material.js'; -import { ShaderChunks } from '../../scene/shader-lib/shader-chunks.js'; import { ShaderUtils } from '../../scene/shader-lib/shader-utils.js'; /** @@ -60,6 +58,46 @@ const shaderOutlineExtendPS = /* glsl */ ` } `; +// WGSL version of the outline extend shader +const shaderOutlineExtendWGSL = /* wgsl */ ` + + varying vUv0: vec2f; + + uniform uOffset: vec2f; + uniform uSrcMultiplier: f32; + var source: texture_2d; + var sourceSampler: sampler; + + @fragment + fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + + var pixel: vec4f; + var texel = textureSample(source, sourceSampler, input.vUv0); + let firstTexel = texel; + var diff = texel.a * uniform.uSrcMultiplier; + + pixel = textureSample(source, sourceSampler, input.vUv0 + uniform.uOffset * -2.0); + texel = max(texel, pixel); + diff = max(diff, length(firstTexel.rgb - pixel.rgb)); + + pixel = textureSample(source, sourceSampler, input.vUv0 + uniform.uOffset * -1.0); + texel = max(texel, pixel); + diff = max(diff, length(firstTexel.rgb - pixel.rgb)); + + pixel = textureSample(source, sourceSampler, input.vUv0 + uniform.uOffset * 1.0); + texel = max(texel, pixel); + diff = max(diff, length(firstTexel.rgb - pixel.rgb)); + + pixel = textureSample(source, sourceSampler, input.vUv0 + uniform.uOffset * 2.0); + texel = max(texel, pixel); + diff = max(diff, length(firstTexel.rgb - pixel.rgb)); + + output.color = vec4f(texel.rgb, min(diff, 1.0)); + return output; + } +`; + const _tempFloatArray = new Float32Array(2); const _tempColor = new Color(); @@ -119,8 +157,9 @@ class OutlineRenderer { this.shaderExtend = ShaderUtils.createShader(device, { uniqueName: 'OutlineExtendShader', attributes: { vertex_position: SEMANTIC_POSITION }, - vertexGLSL: ShaderChunks.get(device, SHADERLANGUAGE_GLSL).get('fullscreenQuadVS'), - fragmentGLSL: shaderOutlineExtendPS + vertexChunk: 'fullscreenQuadVS', + fragmentGLSL: shaderOutlineExtendPS, + fragmentWGSL: shaderOutlineExtendWGSL }); this.shaderBlend = ShaderUtils.createShader(device, { diff --git a/src/framework/app-base.js b/src/framework/app-base.js index 077bdedf9f2..d2d233dabe1 100644 --- a/src/framework/app-base.js +++ b/src/framework/app-base.js @@ -977,7 +977,20 @@ class AppBase extends EventHandler { this.systems.fire('postPostInitialize', this.root); this.fire('postinitialize'); - this.tick(); + this.requestAnimationFrame(); + } + + /** + * Request the next animation frame tick. + * + * @ignore + */ + requestAnimationFrame() { + if (this.xr?.session) { + this.frameRequestId = this.xr.session.requestAnimationFrame(this.tick); + } else { + this.frameRequestId = platform.browser || platform.worker ? requestAnimationFrame(this.tick) : null; + } } /** @@ -1012,15 +1025,30 @@ class AppBase extends EventHandler { update(dt) { this.frame++; + Debug.call(() => { + this.assets.log(); + }); + this.graphicsDevice.update(); // #if _PROFILER this.stats.frame.updateStart = now(); // #endif + // script update + this.stats.frame.scriptUpdateStart = now(); this.systems.fire(this._inTools ? 'toolsUpdate' : 'update', dt); + this.stats.frame.scriptUpdate = now() - this.stats.frame.scriptUpdateStart; + + // animation update + this.stats.frame.animUpdateStart = now(); this.systems.fire('animationUpdate', dt); + this.stats.frame.animUpdate = now() - this.stats.frame.animUpdateStart; + + // post update + this.stats.frame.scriptPostUpdateStart = now(); this.systems.fire('postUpdate', dt); + this.stats.frame.scriptPostUpdate = now() - this.stats.frame.scriptPostUpdateStart; // fire update event this.fire('update', dt); @@ -1065,9 +1093,7 @@ class AppBase extends EventHandler { this.fire('postrender'); - // #if _PROFILER this.stats.frame.renderTime = now() - this.stats.frame.renderStart; - // #endif this.graphicsDevice.frameEnd(); } @@ -1843,6 +1869,10 @@ class AppBase extends EventHandler { this.fire('destroy', this); // fire destroy event this.off('librariesloaded'); + // Clean up gsplat sort timing event listener + this._gsplatSortedEvt?.off(); + this._gsplatSortedEvt = null; + if (typeof document !== 'undefined') { document.removeEventListener('visibilitychange', this._visibilityChangeHandler, false); document.removeEventListener('mozvisibilitychange', this._visibilityChangeHandler, false); @@ -1893,15 +1923,6 @@ class AppBase extends EventHandler { this.scene.layers.destroy(); } - // destroy all texture resources - const assets = this.assets.list(); - for (let i = 0; i < assets.length; i++) { - assets[i].unload(); - assets[i].off(); - } - this.assets.off(); - - // destroy bundle registry this.bundles.destroy(); this.bundles = null; @@ -1915,9 +1936,6 @@ class AppBase extends EventHandler { this.loader.destroy(); this.loader = null; - this.scene.destroy(); - this.scene = null; - this.systems = null; this.context = null; @@ -1949,6 +1967,18 @@ class AppBase extends EventHandler { this.renderer.destroy(); this.renderer = null; + // destroy all resources. Do this after managers have been destroyed + const assets = this.assets.list(); + for (let i = 0; i < assets.length; i++) { + assets[i].unload(); + assets[i].off(); + } + this.assets.off(); + + // destroy scene after assets are unloaded (components need scene.layers during asset cleanup) + this.scene.destroy(); + this.scene = null; + this.graphicsDevice.destroy(); this.graphicsDevice = null; @@ -1972,7 +2002,7 @@ class AppBase extends EventHandler { static cancelTick(app) { if (app.frameRequestId) { - window.cancelAnimationFrame(app.frameRequestId); + cancelAnimationFrame(app.frameRequestId); app.frameRequestId = undefined; } } @@ -1994,6 +2024,11 @@ class AppBase extends EventHandler { */ _registerSceneImmediate(scene) { this.on('postrender', scene.immediate.onPostRender, scene.immediate); + + // Listen for gsplat sort timing events and accumulate + this._gsplatSortedEvt = scene.on('gsplat:sorted', (sortTime) => { + this.stats.frame.gsplatSort += sortTime; + }); } } @@ -2038,11 +2073,7 @@ const makeTick = function (_app) { application._time = currentTime; // Submit a request to queue up a new animation frame immediately - if (application.xr?.session) { - application.frameRequestId = application.xr.session.requestAnimationFrame(application.tick); - } else { - application.frameRequestId = platform.browser || platform.worker ? requestAnimationFrame(application.tick) : null; - } + application.requestAnimationFrame(); if (application.graphicsDevice.contextLost) { return; @@ -2085,6 +2116,7 @@ const makeTick = function (_app) { } application.fire('frameend'); + application.stats.frameEnd(); } application._inFrameUpdate = false; diff --git a/src/framework/asset/asset-registry.js b/src/framework/asset/asset-registry.js index 9caa928f71a..c5d94afcdf2 100644 --- a/src/framework/asset/asset-registry.js +++ b/src/framework/asset/asset-registry.js @@ -1,5 +1,7 @@ import { path } from '../../core/path.js'; import { Debug } from '../../core/debug.js'; +import { Tracing } from '../../core/tracing.js'; +import { TRACEID_ASSETS } from '../../core/constants.js'; import { EventHandler } from '../../core/event-handler.js'; import { TagsCache } from '../../core/tags-cache.js'; import { standardMaterialTextureParameters } from '../../scene/materials/standard-material-parameters.js'; @@ -220,6 +222,16 @@ class AssetRegistry extends EventHandler { this._loader = loader; } + /** + * The ResourceLoader used to load asset files. + * + * @type {ResourceLoader} + * @ignore + */ + get loader() { + return this._loader; + } + /** * Create a filtered list of assets from the registry. * @@ -236,7 +248,7 @@ class AssetRegistry extends EventHandler { } /** - * Add an asset to the registry. + * Add an asset to the registry. If {@link Asset#preload} is `true`, it will also get loaded. * * @param {Asset} asset - The asset to add. * @example @@ -454,7 +466,10 @@ class AssetRegistry extends EventHandler { // remove old element document.head.removeChild(handler._cache[asset.id]); } - handler._cache[asset.id] = extra; + // prevents setting a null value in cache for esm scripts + if (extra) { + handler._cache[asset.id] = extra; + } } _opened(resource); @@ -774,6 +789,48 @@ class AssetRegistry extends EventHandler { if (!type) return results; return results.filter(asset => asset.type === type); } + + /** + * Logs all assets in the registry to the console. Used for debugging with TRACEID_ASSETS. + * + * @ignore + */ + log() { + // #if _DEBUG + if (!Tracing.get(TRACEID_ASSETS)) return; + + const assets = this.list(); + Debug.trace(TRACEID_ASSETS, `Assets: ${assets.length}`); + + // Count by type and status + const byType = {}; + let loadedCount = 0; + let loadingCount = 0; + + assets.forEach((asset, index) => { + // Count by type + byType[asset.type] = (byType[asset.type] || 0) + 1; + + // Count by status + if (asset.loaded) loadedCount++; + else if (asset.loading) loadingCount++; + + // Determine status string + const status = asset.loaded ? 'loaded' : (asset.loading ? 'loading' : 'pending'); + + // Get URL (skip if same as name to avoid duplication) + const url = asset.file?.url; + const urlPart = (url && url !== asset.name) ? ` ${url}` : ''; + + Debug.trace(TRACEID_ASSETS, `${index}. ID:${asset.id} [${asset.type}] "${asset.name}" ${status}${urlPart}`); + }); + + // Log summary + const pendingCount = assets.length - loadedCount - loadingCount; + Debug.trace(TRACEID_ASSETS, `Status: ${loadedCount} loaded, ${loadingCount} loading, ${pendingCount} pending`); + Debug.trace(TRACEID_ASSETS, `Types: ${Object.entries(byType).map(([type, count]) => `${type}:${count}`).join(', ')}`); + // #endif + } } export { AssetRegistry }; diff --git a/src/framework/asset/asset.js b/src/framework/asset/asset.js index f4992b1e831..7ce841aaa2f 100644 --- a/src/framework/asset/asset.js +++ b/src/framework/asset/asset.js @@ -424,7 +424,7 @@ class Asset extends EventHandler { /** * Sets whether to preload an asset. If true, the asset will be loaded during the preload phase - * of application set up. + * of application initialization or when calling {@link AssetRegistry#add}. * * @type {boolean} */ diff --git a/src/framework/components/gsplat/component.js b/src/framework/components/gsplat/component.js index 5d2a8398e82..5ad5f09e171 100644 --- a/src/framework/components/gsplat/component.js +++ b/src/framework/components/gsplat/component.js @@ -17,7 +17,8 @@ import { GSplatPlacement } from '../../../scene/gsplat-unified/gsplat-placement. /** * The GSplatComponent enables an {@link Entity} to render 3D Gaussian Splats. Splats are always * loaded from {@link Asset}s rather than being created programmatically. The asset type is - * `gsplat` which are in the `.ply` file format. + * `gsplat` which supports multiple file formats including `.ply`, `.sog`, `.meta.json` (SOGS + * format), and `.lod-meta.json` (streaming LOD format). * * You should never need to use the GSplatComponent constructor directly. To add an * GSplatComponent to an {@link Entity}, use {@link Entity#addComponent}: @@ -38,11 +39,37 @@ import { GSplatPlacement } from '../../../scene/gsplat-unified/gsplat-placement. * console.log(entity.gsplat.customAabb); * ``` * + * ## Unified Rendering + * + * The {@link GSplatComponent#unified} property enables unified rendering mode, which provides + * advanced features for Gaussian Splats: + * + * - **Global Sorting**: Multiple splat components are sorted together in a single unified sort, + * eliminating visibility artifacts and popping effects when splat components overlap. + * - **LOD Streaming**: Dynamically loads and renders appropriate levels of detail based on camera + * distance, enabling efficient rendering of massive splat scenes. + * + * ```javascript + * // Enable unified rendering for advanced features + * entity.gsplat.unified = true; + * ``` + * + * Note: The `unified` property can only be changed when the component is disabled. + * * Relevant Engine API examples: * - * - [Loading a Splat](https://playcanvas.github.io/#/gaussian-splatting/simple) - * - [Custom Splat Shaders](https://playcanvas.github.io/#/gaussian-splatting/multi-splat) - * - [Splat picking](https://playcanvas.github.io/#/gaussian-splatting/picking) + * - [Simple Splat Loading](https://playcanvas.github.io/#/gaussian-splatting/simple) + * - [Global Sorting](https://playcanvas.github.io/#/gaussian-splatting/global-sorting) + * - [LOD](https://playcanvas.github.io/#/gaussian-splatting/lod) + * - [LOD Instances](https://playcanvas.github.io/#/gaussian-splatting/lod-instances) + * - [LOD Streaming](https://playcanvas.github.io/#/gaussian-splatting/lod-streaming) + * - [LOD Streaming with Spherical Harmonics](https://playcanvas.github.io/#/gaussian-splatting/lod-streaming-sh) + * - [Multi-Splat](https://playcanvas.github.io/#/gaussian-splatting/multi-splat) + * - [Multi-View](https://playcanvas.github.io/#/gaussian-splatting/multi-view) + * - [Picking](https://playcanvas.github.io/#/gaussian-splatting/picking) + * - [Reveal Effect](https://playcanvas.github.io/#/gaussian-splatting/reveal) + * - [Shader Effects](https://playcanvas.github.io/#/gaussian-splatting/shader-effects) + * - [Spherical Harmonics](https://playcanvas.github.io/#/gaussian-splatting/spherical-harmonics) * * @hideconstructor * @category Graphics @@ -80,6 +107,15 @@ class GSplatComponent extends Component { */ _lodDistances = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60]; + /** + * Target number of splats to render for this component. The system will adjust LOD levels + * bidirectionally to reach this budget. Set to 0 to disable (default). + * + * @type {number} + * @private + */ + _splatBudget = 0; + /** * @type {BoundingBox|null} * @private @@ -294,18 +330,34 @@ class GSplatComponent extends Component { set castShadows(value) { if (this._castShadows !== value) { + const layers = this.layers; + const scene = this.system.app.scene; + + // Handle unified mode placement + if (this._placement) { + if (value) { + // Add to shadow casters + for (let i = 0; i < layers.length; i++) { + const layer = scene.layers.getLayerById(layers[i]); + layer?.addGSplatShadowCaster(this._placement); + } + } else { + // Remove from shadow casters + for (let i = 0; i < layers.length; i++) { + const layer = scene.layers.getLayerById(layers[i]); + layer?.removeGSplatShadowCaster(this._placement); + } + } + } + // Handle non-unified mode mesh instance const mi = this.instance?.meshInstance; if (mi) { - const layers = this.layers; - const scene = this.system.app.scene; if (this._castShadows && !value) { for (let i = 0; i < layers.length; i++) { const layer = scene.layers.getLayerById(this.layers[i]); - if (layer) { - layer.removeShadowCasters([mi]); - } + layer?.removeShadowCasters([mi]); } } @@ -314,9 +366,7 @@ class GSplatComponent extends Component { if (!this._castShadows && value) { for (let i = 0; i < layers.length; i++) { const layer = scene.layers.getLayerById(layers[i]); - if (layer) { - layer.addShadowCasters([mi]); - } + layer?.addShadowCasters([mi]); } } } @@ -356,6 +406,38 @@ class GSplatComponent extends Component { return this._lodDistances ? this._lodDistances.slice() : null; } + /** + * Sets the target number of splats to render for this component. The system will adjust LOD + * levels bidirectionally to reach this budget: + * - When over budget: degrades quality for less important geometry + * - When under budget: upgrades quality for more important geometry + * + * This ensures optimal use of available rendering budget while prioritizing quality for + * closer/more important geometry. + * + * Set to 0 to disable the budget (default). When disabled, optimal LOD is determined purely + * by distance and configured LOD parameters. + * + * Only applies to octree-based gsplat rendering in unified mode. + * + * @type {number} + */ + set splatBudget(value) { + this._splatBudget = value; + if (this._placement) { + this._placement.splatBudget = this._splatBudget; + } + } + + /** + * Gets the splat budget limit for this component. + * + * @type {number} + */ + get splatBudget() { + return this._splatBudget; + } + /** * Sets whether to use the unified gsplat rendering. Can be changed only when the component is * not enabled. Default is false. @@ -470,7 +552,13 @@ class GSplatComponent extends Component { if (this._placement) { const layers = this.system.app.scene.layers; for (let i = 0; i < this._layers.length; i++) { - layers.getLayerById(this._layers[i])?.addGSplatPlacement(this._placement); + const layer = layers.getLayerById(this._layers[i]); + if (layer) { + layer.addGSplatPlacement(this._placement); + if (this._castShadows) { + layer.addGSplatShadowCaster(this._placement); + } + } } return; } @@ -489,7 +577,11 @@ class GSplatComponent extends Component { if (this._placement) { const layers = this.system.app.scene.layers; for (let i = 0; i < this._layers.length; i++) { - layers.getLayerById(this._layers[i])?.removeGSplatPlacement(this._placement); + const layer = layers.getLayerById(this._layers[i]); + if (layer) { + layer.removeGSplatPlacement(this._placement); + layer.removeGSplatShadowCaster(this._placement); + } } return; } @@ -634,6 +726,12 @@ class GSplatComponent extends Component { if (asset) { this._placement = new GSplatPlacement(asset.resource, this.entity); this._placement.lodDistances = this._lodDistances; + this._placement.splatBudget = this._splatBudget; + + // add placement to layers if component is enabled + if (this.enabled && this.entity.enabled) { + this.addToLayers(); + } } } else { @@ -642,7 +740,8 @@ class GSplatComponent extends Component { if (asset) { this.instance = new GSplatInstance(asset.resource, { material: this._materialTmp, - highQualitySH: this._highQualitySH + highQualitySH: this._highQualitySH, + scene: this.system.app.scene }); this._materialTmp = null; } diff --git a/src/framework/components/gsplat/gsplat-asset-loader.js b/src/framework/components/gsplat/gsplat-asset-loader.js index b5e6f4aa529..87ea02c7f88 100644 --- a/src/framework/components/gsplat/gsplat-asset-loader.js +++ b/src/framework/components/gsplat/gsplat-asset-loader.js @@ -71,6 +71,14 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { */ _retryCount = new Map(); + /** + * Whether this asset loader has been destroyed. + * + * @type {boolean} + * @private + */ + _destroyed = false; + /** * Create a new GSplatAssetLoader. * @@ -81,6 +89,44 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { this._registry = registry; } + + /** + * Destroys the asset loader and force-unloads all tracked assets, ignoring ref counts. + * This is used when the octree resource itself is being destroyed. + */ + destroy() { + this._destroyed = true; + + // Force-unload all tracked assets + for (const asset of this._urlToAsset.values()) { + // Fire 'unload' event to trigger cleanup in parsers (like sogs.js) + asset.fire('unload', asset); + + // Remove event listeners + asset.off('load'); + asset.off('error'); + + this._registry.remove(asset); + asset.unload(); + } + + this._urlToAsset.clear(); + this._loadQueue.length = 0; + this._currentlyLoading.clear(); + this._retryCount.clear(); + } + + /** + * Checks if the loader can start new loads. Returns false if the 'gsplat' handler + * has been removed from the registry (e.g., during app destruction). + * + * @returns {boolean} True if loading is possible, false otherwise. + * @private + */ + _canLoad() { + return !!this._registry.loader?.getHandler('gsplat'); + } + /** * Initiates loading of a gsplat asset. This is a fire-and-forget operation that starts * the loading process. Use getResource() later to check if the asset has finished loading. @@ -124,14 +170,17 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { let asset = this._urlToAsset.get(url); if (!asset) { - // Check if the asset registry already has this asset - asset = this._registry.getByUrl(url); + // Create a new gsplat asset + // @ts-ignore - minimalMemory is a custom option for gsplat assets + asset = new Asset(url, 'gsplat', { url }, {}, { minimalMemory: true }); - if (!asset) { - // Create a new gsplat asset - asset = new Asset(url, 'gsplat', { url }); - this._registry.add(asset); - } + // Assert that registry doesn't already have an asset for this URL + // If it does, there's a code ownership issue - GSplatAssetLoader should be the only + // creator of gsplat assets with these URLs + Debug.assert(!this._registry.getByUrl(url), + `Asset with URL ${url} already exists in registry but not tracked by GSplatAssetLoader`); + + this._registry.add(asset); // Track this asset in our map this._urlToAsset.set(url, asset); @@ -155,6 +204,11 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { * @private */ _onAssetLoadSuccess(url, asset) { + // Don't process if destroyed or already unloaded + if (this._destroyed || !this._urlToAsset.has(url)) { + return; + } + // Remove from currently loading this._currentlyLoading.delete(url); @@ -174,6 +228,11 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { * @private */ _onAssetLoadError(url, asset, err) { + // Don't process if destroyed, handler removed, or already unloaded + if (this._destroyed || !this._canLoad() || !this._urlToAsset.has(url)) { + return; + } + const retryCount = this._retryCount.get(url) || 0; if (retryCount < this.maxRetries) { @@ -208,6 +267,11 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { * @private */ _processQueue() { + // Don't process queue if destroyed or handler removed + if (this._destroyed || !this._canLoad()) { + return; + } + while (this._currentlyLoading.size < this.maxConcurrentLoads && this._loadQueue.length > 0) { const url = this._loadQueue.shift(); if (url) { @@ -238,6 +302,12 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase { // Unload the asset const asset = this._urlToAsset.get(url); if (asset) { + // IMPORTANT: Fire 'unload' event explicitly before calling asset.unload() + // This ensures parsers with async loading (like sogs.js) can clean up + // even if the asset hasn't finished loading yet + // NOTE: Must fire BEFORE removing event listeners + asset.fire('unload', asset); + // Remove event listeners asset.off('load'); asset.off('error'); diff --git a/src/framework/components/gsplat/system.js b/src/framework/components/gsplat/system.js index 9330e80bdfa..c37eb9ed40a 100644 --- a/src/framework/components/gsplat/system.js +++ b/src/framework/components/gsplat/system.js @@ -1,3 +1,4 @@ +import { Debug } from '../../../core/debug.js'; import { Vec3 } from '../../../core/math/vec3.js'; import { BoundingBox } from '../../../core/shape/bounding-box.js'; import { GSplatDirector } from '../../../scene/gsplat-unified/gsplat-director.js'; @@ -5,12 +6,20 @@ import { Component } from '../component.js'; import { ComponentSystem } from '../system.js'; import { GSplatComponent } from './component.js'; import { GSplatComponentData } from './data.js'; -import { GSplatAssetLoader } from './gsplat-asset-loader.js'; import { gsplatChunksGLSL } from '../../../scene/shader-lib/glsl/collections/gsplat-chunks-glsl.js'; import { gsplatChunksWGSL } from '../../../scene/shader-lib/wgsl/collections/gsplat-chunks-wgsl.js'; import { SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../../platform/graphics/constants.js'; import { ShaderChunks } from '../../../scene/shader-lib/shader-chunks.js'; +// Register deprecation warning for old customization chunk +Debug.call(() => { + ShaderChunks.registerValidation('gsplatCustomizeVS', { + message: 'Shader chunk gsplatCustomizeVS is deprecated. Use gsplatModifyVS for better performance.', + defaultCodeGLSL: gsplatChunksGLSL.gsplatCustomizeVS, + defaultCodeWGSL: gsplatChunksWGSL.gsplatCustomizeVS + }); +}); + /** * @import { AppBase } from '../../app-base.js' * @import { Camera } from '../../../scene/camera.js' @@ -42,7 +51,8 @@ class GSplatComponentSystem extends ComponentSystem { /** * Fired when a GSplat material is created for a camera and layer combination. In unified * mode, materials are created during the first frame update when the GSplat is rendered. - * The handler is passed the {@link ShaderMaterial}, the {@link Camera}, and the {@link Layer}. + * The handler is passed the {@link ShaderMaterial}, the {@link CameraComponent}, and + * the {@link Layer}. * * This event is useful for setting up custom material chunks and parameters before the * first render. @@ -57,6 +67,41 @@ class GSplatComponentSystem extends ComponentSystem { */ static EVENT_MATERIALCREATED = 'material:created'; + /** + * Fired every frame for each camera and layer combination rendering GSplats in unified mode. + * The handler is passed the {@link CameraComponent}, the {@link Layer}, a boolean indicating + * if the current frame has up-to-date sorting, and a number indicating how many resources are + * loading. + * + * The `ready` parameter indicates whether the current frame reflects all recent changes (camera + * movement, splat transforms, lod updates, etc.) with the latest sorting applied. The `loadingCount` + * parameter reports the total number of octree LOD resources currently loading or queued to load. + * + * This event is useful for video capture or other workflows that need to wait for frames + * to be fully ready. Only capture frames and move camera to next position when both + * `ready === true` and `loadingCount === 0`. Note that `loadingCount` can be used as a boolean + * in conditionals (0 is falsy, non-zero is truthy) for backward compatibility. + * + * @event + * @example + * // Wait for frame to be ready before capturing + * app.systems.gsplat.on('frame:ready', (camera, layer, ready, loadingCount) => { + * if (ready && !loadingCount) { + * console.log(`Frame ready to capture for camera ${camera.entity.name}`); + * // Capture frame here + * } + * }); + * @example + * // Track loading progress (0..1) + * let maxLoadingCount = 0; + * app.systems.gsplat.on('frame:ready', (camera, layer, ready, loadingCount) => { + * maxLoadingCount = Math.max(maxLoadingCount, loadingCount); + * const progress = maxLoadingCount > 0 ? (maxLoadingCount - loadingCount) / maxLoadingCount : 1; + * console.log(`Loading progress: ${(progress * 100).toFixed(1)}%`); + * }); + */ + static EVENT_FRAMEREADY = 'frame:ready'; + /** * Create a new GSplatComponentSystem. * @@ -73,9 +118,7 @@ class GSplatComponentSystem extends ComponentSystem { this.schema = _schema; - // loader for splat LOD assets, as asset system is not available on the scene level - const gsplatAssetLoader = new GSplatAssetLoader(app.assets); - app.renderer.gsplatDirector = new GSplatDirector(app.graphicsDevice, app.renderer, app.scene, gsplatAssetLoader, this); + app.renderer.gsplatDirector = new GSplatDirector(app.graphicsDevice, app.renderer, app.scene, this); // register gsplat shader chunks ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_GLSL).add(gsplatChunksGLSL); diff --git a/src/framework/components/registry.js b/src/framework/components/registry.js index c74b31e7fd2..ce94325b787 100644 --- a/src/framework/components/registry.js +++ b/src/framework/components/registry.js @@ -8,6 +8,7 @@ import { EventHandler } from '../../core/event-handler.js'; * @import { CameraComponentSystem } from './camera/system.js' * @import { CollisionComponentSystem } from './collision/system.js' * @import { ElementComponentSystem } from './element/system.js' + * @import { GSplatComponentSystem } from './gsplat/system.js' * @import { JointComponentSystem } from './joint/system.js' * @import { LayoutChildComponentSystem } from './layout-child/system.js' * @import { LayoutGroupComponentSystem } from './layout-group/system.js' @@ -95,6 +96,14 @@ class ComponentSystemRegistry extends EventHandler { */ element; + /** + * Gets the {@link GSplatComponentSystem} from the registry. + * + * @type {GSplatComponentSystem|undefined} + * @readonly + */ + gsplat; + /** * Gets the {@link JointComponentSystem} from the registry. * diff --git a/src/framework/components/rigid-body/system.js b/src/framework/components/rigid-body/system.js index 36192f1fddd..76fcc564cde 100644 --- a/src/framework/components/rigid-body/system.js +++ b/src/framework/components/rigid-body/system.js @@ -1053,9 +1053,7 @@ class RigidBodyComponentSystem extends ComponentSystem { onUpdate(dt) { let i, len; - // #if _PROFILER this._stats.physicsStart = now(); - // #endif // downcast gravity to float32 so we can accurately compare with existing // gravity set in ammo. @@ -1101,9 +1099,7 @@ class RigidBodyComponentSystem extends ComponentSystem { this._checkForCollisions(Ammo.getPointer(this.dynamicsWorld), dt); } - // #if _PROFILER this._stats.physicsTime = now() - this._stats.physicsStart; - // #endif } destroy() { diff --git a/src/framework/components/screen/component.js b/src/framework/components/screen/component.js index 76f68dd4394..27ed3dc80fd 100644 --- a/src/framework/components/screen/component.js +++ b/src/framework/components/screen/component.js @@ -1,4 +1,5 @@ import { Debug } from '../../../core/debug.js'; +import { math } from '../../../core/math/math.js'; import { Mat4 } from '../../../core/math/mat4.js'; import { Vec2 } from '../../../core/math/vec2.js'; import { Entity } from '../../entity.js'; @@ -355,16 +356,14 @@ class ScreenComponent extends Component { /** * Sets the screen's render priority. Priority determines the order in which ScreenComponents - * in the same layer are rendered. Number must be an integer between 0 and 255. Priority is set + * in the same layer are rendered. Number must be an integer between 0 and 127. Priority is set * into the top 8 bits of the {@link ElementComponent#drawOrder} property. Defaults to 0. * * @type {number} */ set priority(value) { - if (value > 0xFF) { - Debug.warn(`Clamping screen priority from ${value} to 255`); - value = 0xFF; - } + Debug.assert(value >= 0 && value <= 0x7F, `Screen priority must be between 0 and 127, got ${value}`); + value = math.clamp(value, 0, 0x7F); if (this._priority === value) { return; } diff --git a/src/framework/components/screen/system.js b/src/framework/components/screen/system.js index 2282a50be89..58a1a196193 100644 --- a/src/framework/components/screen/system.js +++ b/src/framework/components/screen/system.js @@ -68,11 +68,29 @@ class ScreenComponentSystem extends ComponentSystem { component.referenceResolution = component._referenceResolution; } + // Update any existing element components in the hierarchy that don't have a screen yet. + // This handles cases where element components were added before the screen component. + this._updateDescendantElements(component.entity, component.entity); + // queue up a draw order sync component.syncDrawOrder(); super.initializeComponentData(component, data, _schema); } + _updateDescendantElements(entity, screenEntity) { + const children = entity.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child.element && !child.element.screen) { + child.element._updateScreen(screenEntity); + } + // Continue traversing unless this child has its own screen component + if (!child.screen) { + this._updateDescendantElements(child, screenEntity); + } + } + } + destroy() { super.destroy(); diff --git a/src/framework/graphics/picker.js b/src/framework/graphics/picker.js index 0b674d63fab..c4781bcd425 100644 --- a/src/framework/graphics/picker.js +++ b/src/framework/graphics/picker.js @@ -6,7 +6,9 @@ import { Layer } from '../../scene/layer.js'; import { Debug } from '../../core/debug.js'; import { RenderPassPicker } from './render-pass-picker.js'; import { math } from '../../core/math/math.js'; +import { Vec3 } from '../../core/math/vec3.js'; import { Vec4 } from '../../core/math/vec4.js'; +import { Mat4 } from '../../core/math/mat4.js'; /** * @import { AppBase } from '../app-base.js' @@ -17,25 +19,125 @@ import { Vec4 } from '../../core/math/vec4.js'; const tempSet = new Set(); const _rect = new Vec4(); +const _floatView = new Float32Array(1); +const _int32View = new Int32Array(_floatView.buffer); /** - * Picker object used to select mesh instances from screen coordinates. + * Picker object used to select mesh instances from screen coordinates. It can also optionally + * capture depth information to determine world positions of picked points. * - * @property {number} width Width of the pick buffer in pixels (read-only). - * @property {number} height Height of the pick buffer in pixels (read-only). - * @property {RenderTarget} renderTarget The render target used by the picker internally - * (read-only). + * The picker works by rendering mesh instances to an offscreen render target with unique IDs + * encoded as colors. When queried, it reads back the pixel data to identify which mesh instance + * was at the specified screen coordinates. If depth picking is enabled, it also captures depth + * values to compute world positions. + * + * **Main API methods:** + * - {@link Picker#prepare} - Renders the pick buffer (call once per frame before picking) + * - {@link Picker#getSelectionAsync} - Get mesh instances in a screen area + * - {@link Picker#getWorldPointAsync} - Get world position at screen coordinates (requires depth) + * + * **Performance considerations:** + * The picker resolution can be set lower than the screen resolution for better performance, + * though this reduces picking precision and may miss small objects. + * + * @example + * // Create a picker with depth picking enabled at quarter resolution + * const picker = new pc.Picker(app, canvas.width * 0.25, canvas.height * 0.25, true); + * + * // In your update loop, prepare the picker + * picker.resize(canvas.width * 0.25, canvas.height * 0.25); + * picker.prepare(camera, scene); + * + * // Pick mesh instances in an area + * picker.getSelectionAsync(x, y, width, height).then((meshInstances) => { + * meshInstances.forEach((meshInstance) => { + * console.log('Picked:', meshInstance.node.name); + * }); + * }); + * + * // Pick world position (requires depth enabled) + * picker.getWorldPointAsync(x, y).then((worldPoint) => { + * if (worldPoint) { + * console.log(worldPoint); + * } + * }); + * + * @see {@link http://playcanvas.github.io/#/graphics/area-picker|Area Picker Example} + * @see {@link https://playcanvas.github.io/#gaussian-splatting/picking|Gaussian Splatting Picking Example} * * @category Graphics */ class Picker { - // internal render target + /** + * @type {import('../../platform/graphics/graphics-device.js').GraphicsDevice} + * @private + */ + device; + + /** + * @type {RenderPassPicker} + * @private + */ + renderPass; + + /** + * @type {boolean} + * @private + */ + depth; + + /** @type {number} */ + width; + + /** @type {number} */ + height; + + /** + * Internal render target. + * + * @type {RenderTarget|null} + * @private + */ renderTarget = null; - // mapping table from ids to meshInstances + /** + * Color buffer texture for pick IDs. + * + * @type {Texture|null} + * @private + */ + colorBuffer = null; + + /** + * Optional depth buffer texture for depth picking. + * + * @type {Texture|null} + * @private + */ + depthBuffer = null; + + /** + * Internal render target for reading the depth buffer. + * + * @type {RenderTarget|null} + * @private + */ + renderTargetDepth = null; + + /** + * Mapping table from ids to meshInstances. + * + * @type {Map} + * @private + */ mapping = new Map(); - // when the device is destroyed, this allows us to ignore async results + /** + * When the device is destroyed, this allows us to ignore async results. + * + * @type {boolean} + * @private + */ deviceValid = true; /** @@ -44,20 +146,22 @@ class Picker { * @param {AppBase} app - The application managing this picker instance. * @param {number} width - The width of the pick buffer in pixels. * @param {number} height - The height of the pick buffer in pixels. + * @param {boolean} [depth] - Whether to enable depth picking. When enabled, depth + * information is captured alongside mesh IDs using MRT. Defaults to false. */ - constructor(app, width, height) { - Debug.assert(app); - + constructor(app, width, height, depth = false) { // Note: The only reason this class needs the app is to access the renderer. Ideally we remove this dependency and move // the Picker from framework to the scene level, or even the extras. - this.renderer = app.renderer; + Debug.assert(app); this.device = app.graphicsDevice; this.renderPass = new RenderPassPicker(this.device, app.renderer); + this.depth = depth; this.width = 0; this.height = 0; this.resize(width, height); + this.allocateRenderTarget(); // handle the device getting destroyed this.device.on('destroy', () => { @@ -65,6 +169,14 @@ class Picker { }); } + /** + * Frees resources associated with this picker. + */ + destroy() { + this.releaseRenderTarget(); + this.renderPass?.destroy(); + } + /** * Return the list of mesh instances selected by the specified rectangle in the previously * prepared pick buffer. The rectangle using top-left coordinate system. @@ -128,21 +240,116 @@ class Picker { * }); */ getSelectionAsync(x, y, width = 1, height = 1) { + if (!this.renderTarget || !this.renderTarget.colorBuffer) { + return Promise.resolve([]); + } + return this._readTexture(this.renderTarget.colorBuffer, x, y, width, height, this.renderTarget).then((pixels) => { + return this.decodePixels(pixels, this.mapping); + }); + } + /** + * Helper method to read pixels from a texture asynchronously. + * + * @param {Texture} texture - The texture to read from. + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @param {number} width - The width of the rectangle. + * @param {number} height - The height of the rectangle. + * @param {RenderTarget} renderTarget - The render target to use for reading. + * @returns {Promise} Promise resolving to the pixel data. + * @private + */ + _readTexture(texture, x, y, width, height, renderTarget) { if (this.device?.isWebGL2) { - y = this.renderTarget.height - (y + height); + y = renderTarget.height - (y + height); } const rect = this.sanitizeRect(x, y, width, height); - return this.renderTarget.colorBuffer.read(rect.x, rect.y, rect.z, rect.w, { - renderTarget: this.renderTarget, - immediate: true - }).then((pixels) => { - return this.decodePixels(pixels, this.mapping); + // @ts-ignore + return texture.read(rect.x, rect.y, rect.z, rect.w, { + immediate: true, + renderTarget: renderTarget }); } - // sanitize the rectangle to make sure it;s inside the texture and does not use fractions + /** + * Return the world position of the mesh instance picked at the specified screen coordinates. + * + * @param {number} x - The x coordinate of the pixel to pick. + * @param {number} y - The y coordinate of the pixel to pick. + * @returns {Promise} Promise that resolves with the world position of the picked point, + * or null if no depth is available or nothing was picked. + * @example + * // Get the world position at screen coordinates (100, 50) + * picker.getWorldPointAsync(100, 50).then((worldPoint) => { + * if (worldPoint) { + * console.log('World position:', worldPoint); + * // Use the world position + * } else { + * console.log('No object at this position'); + * } + * }); + */ + async getWorldPointAsync(x, y) { + // get the camera from the render pass + const camera = this.renderPass.camera; + if (!camera) { + return null; + } + + // capture the inverse view-projection matrix synchronously before awaiting + const viewProjMat = new Mat4().mul2(camera.camera.projectionMatrix, camera.camera.viewMatrix); + const invViewProj = viewProjMat.invert(); + + const depth = await this.getPointDepthAsync(x, y); + if (depth === null) { + return null; + } + + // unproject to world space using the captured matrix + const deviceCoord = new Vec4( + (x / this.width) * 2 - 1, + (1 - y / this.height) * 2 - 1, + depth * 2 - 1, + 1.0 + ); + invViewProj.transformVec4(deviceCoord, deviceCoord); + deviceCoord.mulScalar(1.0 / deviceCoord.w); + + return new Vec3(deviceCoord.x, deviceCoord.y, deviceCoord.z); + } + + /** + * Return the depth value of the mesh instance picked at the specified screen coordinates. + * + * @param {number} x - The x coordinate of the pixel to pick. + * @param {number} y - The y coordinate of the pixel to pick. + * @returns {Promise} Promise that resolves with the depth value of the picked point + * (in 0..1 range), or null if depth picking is not enabled or no object was picked. + * @ignore + */ + async getPointDepthAsync(x, y) { + if (!this.depthBuffer) { + return null; + } + + const pixels = await this._readTexture(this.depthBuffer, x, y, 1, 1, this.renderTargetDepth); + + // reconstruct uint bits from RGBA8 + const intBits = (pixels[0] << 24) | (pixels[1] << 16) | (pixels[2] << 8) | pixels[3]; + + // check for white (cleared) depth + if (intBits === 0xFFFFFFFF) { + return null; + } + + // reinterpret bits as float + _int32View[0] = intBits; + return _floatView[0]; + } + + // sanitize the rectangle to make sure it's inside the texture and does not use fractions sanitizeRect(x, y, width, height) { const maxWidth = this.renderTarget.width; const maxHeight = this.renderTarget.height; @@ -188,11 +395,8 @@ class Picker { return selection; } - allocateRenderTarget() { - - // TODO: Ideally we'd use a UINT32 texture format and avoid RGBA8 conversion, but WebGL2 does not - // support clearing render targets of this format, so we'd need a quad based clear solution. - const colorBuffer = new Texture(this.device, { + createTexture(name) { + return new Texture(this.device, { format: PIXELFORMAT_RGBA8, width: this.width, height: this.height, @@ -201,21 +405,45 @@ class Picker { magFilter: FILTER_NEAREST, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, - name: 'pick' + name: name }); + } + + allocateRenderTarget() { + + // TODO: Ideally we'd use a UINT32 texture format and avoid RGBA8 conversion, but WebGL2 does not + // support clearing render targets of this format, so we'd need a quad based clear solution. + this.colorBuffer = this.createTexture('pick'); + const colorBuffers = [this.colorBuffer]; + + if (this.depth) { + // create depth buffer for MRT + this.depthBuffer = this.createTexture('pick-depth'); + colorBuffers.push(this.depthBuffer); + + // create a render target for reading the depth buffer + this.renderTargetDepth = new RenderTarget({ + colorBuffer: this.depthBuffer, + depth: false + }); + } this.renderTarget = new RenderTarget({ - colorBuffer: colorBuffer, + colorBuffers: colorBuffers, depth: true }); } releaseRenderTarget() { - if (this.renderTarget) { - this.renderTarget.destroyTextureBuffers(); - this.renderTarget.destroy(); - this.renderTarget = null; - } + this.renderTarget?.destroyTextureBuffers(); + this.renderTarget?.destroy(); + this.renderTarget = null; + + this.renderTargetDepth?.destroy(); + this.renderTargetDepth = null; + + this.colorBuffer = null; + this.depthBuffer = null; } /** @@ -236,10 +464,8 @@ class Picker { } // make the render target the right size - if (!this.renderTarget || (this.width !== this.renderTarget.width || this.height !== this.renderTarget.height)) { - this.releaseRenderTarget(); - this.allocateRenderTarget(); - } + this.renderTarget?.resize(this.width, this.height); + this.renderTargetDepth?.resize(this.width, this.height); // clear registered meshes mapping this.mapping.clear(); @@ -247,13 +473,12 @@ class Picker { const renderPass = this.renderPass; renderPass.init(this.renderTarget); - // set up clears - renderPass.colorOps.clearValue = Color.WHITE; - renderPass.colorOps.clear = true; + // set up clears - setClearColor handles MRT and clears all color buffers + renderPass.setClearColor(Color.WHITE); renderPass.depthStencilOps.clearDepth = true; // render the pass to update the render target - renderPass.update(camera, scene, layers, this.mapping); + renderPass.update(camera, scene, layers, this.mapping, this.depth); renderPass.render(); } diff --git a/src/framework/graphics/render-pass-picker.js b/src/framework/graphics/render-pass-picker.js index 62041511650..fc65a956d0a 100644 --- a/src/framework/graphics/render-pass-picker.js +++ b/src/framework/graphics/render-pass-picker.js @@ -1,7 +1,7 @@ import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; -import { SHADER_PICK } from '../../scene/constants.js'; +import { SHADER_PICK, SHADER_DEPTH_PICK } from '../../scene/constants.js'; /** * @import { BindGroup } from '../../platform/graphics/bind-group.js' @@ -32,11 +32,12 @@ class RenderPassPicker extends RenderPass { this.viewBindGroups.length = 0; } - update(camera, scene, layers, mapping) { + update(camera, scene, layers, mapping, depth) { this.camera = camera; this.scene = scene; this.layers = layers; this.mapping = mapping; + this.depth = depth; if (scene.clusteredLightingEnabled) { this.emptyWorldClusters = this.renderer.worldClustersAllocator.empty; @@ -99,10 +100,13 @@ class RenderPassPicker extends RenderPass { renderer.setCameraUniforms(camera.camera, renderTarget); if (device.supportsUniformBuffers) { + // Initialize view bind group format if not already done + renderer.initViewBindGroupFormat(clusteredLightingEnabled); renderer.setupViewUniformBuffers(this.viewBindGroups, renderer.viewUniformFormat, renderer.viewBindGroupFormat, null); } - renderer.renderForward(camera.camera, renderTarget, tempMeshInstances, lights, SHADER_PICK, (meshInstance) => { + const shaderPass = this.depth ? SHADER_DEPTH_PICK : SHADER_PICK; + renderer.renderForward(camera.camera, renderTarget, tempMeshInstances, lights, shaderPass, (meshInstance) => { device.setBlendState(BlendState.NOBLEND); }); diff --git a/src/framework/lightmapper/render-pass-lightmapper.js b/src/framework/lightmapper/render-pass-lightmapper.js index 692e84f9a18..cb64b81974c 100644 --- a/src/framework/lightmapper/render-pass-lightmapper.js +++ b/src/framework/lightmapper/render-pass-lightmapper.js @@ -36,6 +36,11 @@ class RenderPassLightmapper extends RenderPass { const { renderer, camera, receivers, renderTarget, worldClusters, lightArray } = this; + // Initialize view bind group format if not already done + if (device.supportsUniformBuffers && !renderer.viewUniformFormat) { + renderer.initViewBindGroupFormat(renderer.scene.clusteredLightingEnabled); + } + renderer.renderForwardLayer(camera, renderTarget, null, undefined, SHADER_FORWARD, this.viewBindGroups, { meshInstances: receivers, splitLights: lightArray, diff --git a/src/framework/parsers/gsplat-octree.js b/src/framework/parsers/gsplat-octree.js index c78443bbdad..a9e3887a179 100644 --- a/src/framework/parsers/gsplat-octree.js +++ b/src/framework/parsers/gsplat-octree.js @@ -1,5 +1,6 @@ import { http, Http } from '../../platform/net/http.js'; import { GSplatOctreeResource } from '../../scene/gsplat-unified/gsplat-octree.resource.js'; +import { GSplatAssetLoader } from '../components/gsplat/gsplat-asset-loader.js'; /** * @import { AppBase } from '../app-base.js' @@ -49,7 +50,8 @@ class GSplatOctreeParser { http.get(url.load, options, (err, data) => { if (!err) { // create a resource with the parsed data, passing the asset's file URL - const resource = new GSplatOctreeResource(asset.file.url, data); + const assetLoader = new GSplatAssetLoader(this.app.assets); + const resource = new GSplatOctreeResource(asset.file.url, data, assetLoader); callback(null, resource); } else { callback(`Error loading gsplat octree: ${url.original} [${err}]`); diff --git a/src/framework/parsers/sog-bundle.js b/src/framework/parsers/sog-bundle.js index c5865344935..bc66e342001 100644 --- a/src/framework/parsers/sog-bundle.js +++ b/src/framework/parsers/sog-bundle.js @@ -210,6 +210,8 @@ class SogBundleParser { contents: file.data }, { mipmaps: false + }, { + crossOrigin: 'anonymous' }); } else { // file doesn't exist in bundle, treat it as a url @@ -219,6 +221,8 @@ class SogBundleParser { filename }, { mipmaps: false + }, { + crossOrigin: 'anonymous' }); } @@ -249,7 +253,12 @@ class SogBundleParser { }); // construct the gsplat resource + const decompress = asset.data?.decompress; + const minimalMemory = asset.options?.minimalMemory ?? false; + const data = new GSplatSogsData(); + data.url = url.original; + data.minimalMemory = minimalMemory; data.meta = meta; data.numSplats = meta.count; data.means_l = textures[meta.means.files[0]].resource; @@ -260,8 +269,6 @@ class SogBundleParser { data.sh_centroids = textures[meta.shN?.files[0]]?.resource; data.sh_labels = textures[meta.shN?.files[1]]?.resource; - const decompress = asset.data?.decompress; - if (!decompress) { // no need to prepare gpu data if decompressing await data.prepareGpuData(); diff --git a/src/framework/parsers/sogs.js b/src/framework/parsers/sogs.js index 8a54e8320c2..faae06edd02 100644 --- a/src/framework/parsers/sogs.js +++ b/src/framework/parsers/sogs.js @@ -97,6 +97,20 @@ class SogsParser { this.maxRetries = maxRetries; } + /** + * Checks if loading should be aborted due to asset unload or invalid device. + * + * @param {Asset} asset - The asset being loaded. + * @param {boolean} unloaded - Whether the asset was unloaded during async loading. + * @returns {boolean} True if loading should be aborted. + * @private + */ + _shouldAbort(asset, unloaded) { + if (unloaded || !this.app.assets.get(asset.id)) return true; + if (!this.app?.graphicsDevice || this.app.graphicsDevice._destroyed) return true; + return false; + } + async loadTextures(url, callback, asset, meta) { // transform meta to latest shape if (meta.version !== 2) { @@ -119,6 +133,8 @@ class SogsParser { filename }, { mipmaps: false + }, { + crossOrigin: 'anonymous' }); const promise = new Promise((resolve, reject) => { @@ -135,8 +151,13 @@ class SogsParser { const textureAssets = subs.map(sub => textures[sub]).flat(); + // Track if asset was unloaded during async loading + let unloaded = false; + // When the parent gsplat asset unloads, remove and unload child texture assets asset.once('unload', () => { + unloaded = true; + textureAssets.forEach((t) => { // remove from registry assets.remove(t); @@ -153,8 +174,19 @@ class SogsParser { // wait for all textures to complete loading await Promise.allSettled(promises); + if (this._shouldAbort(asset, unloaded)) { + // Clean up texture assets that were created during the async load + textureAssets.forEach((t) => { + assets.remove(t); + t.unload(); + }); + callback(null, null); + return; + } + // construct the gsplat resource const data = new GSplatSogsData(); + data.url = url.original; data.meta = meta; data.numSplats = meta.count; data.means_l = textures.means[0].resource; @@ -166,20 +198,38 @@ class SogsParser { data.sh_labels = textures.shN?.[1]?.resource; const decompress = asset.data?.decompress; + const minimalMemory = asset.options?.minimalMemory ?? false; + + // Pass minimalMemory to data + data.minimalMemory = minimalMemory; if (!decompress) { - if (!this.app?.graphicsDevice || this.app?.graphicsDevice?._destroyed) return; + if (this._shouldAbort(asset, unloaded)) { + data.destroy(); + callback(null, null); + return; + } // no need to prepare gpu data if decompressing await data.prepareGpuData(); } - if (!this.app?.graphicsDevice || this.app?.graphicsDevice?._destroyed) return; + if (this._shouldAbort(asset, unloaded)) { + data.destroy(); + callback(null, null); + return; + } const resource = decompress ? new GSplatResource(this.app.graphicsDevice, await data.decompress()) : new GSplatSogsResource(this.app.graphicsDevice, data); + if (this._shouldAbort(asset, unloaded)) { + resource.destroy(); + callback(null, null); + return; + } + callback(null, resource); } @@ -212,6 +262,11 @@ class SogsParser { }; http.get(url.load, options, (err, meta) => { + if (this._shouldAbort(asset, false)) { + callback(null, null); + return; + } + if (!err) { this.loadTextures(url, callback, asset, meta); } else { diff --git a/src/framework/script/script-attributes.js b/src/framework/script/script-attributes.js index a84bdd41209..15223bf4de4 100644 --- a/src/framework/script/script-attributes.js +++ b/src/framework/script/script-attributes.js @@ -323,6 +323,16 @@ class ScriptAttributes { * }); */ add(name, args) { + if (!args) { + Debug.error(`Cannot add attribute '${name}' to script type '${this.scriptType.name}': args parameter is required`); + return; + } + + if (!args.type) { + Debug.error(`Cannot add attribute '${name}' to script type '${this.scriptType.name}': args.type is required`); + return; + } + if (this.index[name]) { Debug.warn(`attribute '${name}' is already defined for script type '${this.scriptType.name}'`); return; diff --git a/src/framework/stats.js b/src/framework/stats.js index fb33aa15cf7..6c9c9e12493 100644 --- a/src/framework/stats.js +++ b/src/framework/stats.js @@ -25,6 +25,12 @@ class ApplicationStats { renderTime: 0, physicsStart: 0, physicsTime: 0, + scriptUpdateStart: 0, + scriptUpdate: 0, + scriptPostUpdateStart: 0, + scriptPostUpdate: 0, + animUpdateStart: 0, + animUpdate: 0, cullTime: 0, sortTime: 0, skinTime: 0, @@ -33,6 +39,7 @@ class ApplicationStats { triangles: 0, gsplats: 0, + gsplatSort: 0, otherPrimitives: 0, shaders: 0, materials: 0, @@ -77,6 +84,7 @@ class ApplicationStats { this.shaders = device._shaderStats; this.vram = device._vram; + this.gpu = device.gpuProfiler?.passTimings ?? new Map(); Object.defineProperty(this.vram, 'totalUsed', { get: function () { @@ -103,6 +111,15 @@ class ApplicationStats { const batcher = getApplication()._batcher; return batcher ? batcher._stats : null; } + + /** + * Called at the end of each frame to reset per-frame statistics. + * + * @ignore + */ + frameEnd() { + this.frame.gsplatSort = 0; + } } export { ApplicationStats }; diff --git a/src/framework/xr/xr-manager.js b/src/framework/xr/xr-manager.js index c749ade079b..7ef21178c2f 100644 --- a/src/framework/xr/xr-manager.js +++ b/src/framework/xr/xr-manager.js @@ -740,7 +740,7 @@ class XrManager extends EventHandler { // old requestAnimationFrame will never be triggered, // so queue up new tick if (this.app.systems) { - this.app.tick(); + this.app.requestAnimationFrame(); } }; @@ -773,7 +773,7 @@ class XrManager extends EventHandler { // old requestAnimationFrame will never be triggered, // so queue up new tick - this.app.tick(); + this.app.requestAnimationFrame(); if (callback) callback(null); this.fire('start'); diff --git a/src/index.js b/src/index.js index 314c13628f5..433e21a7219 100644 --- a/src/index.js +++ b/src/index.js @@ -196,6 +196,7 @@ export { EnvLighting } from './scene/graphics/env-lighting.js'; export { PostEffect } from './scene/graphics/post-effect.js'; export { RenderPassColorGrab } from './scene/graphics/render-pass-color-grab.js'; export { RenderPassShaderQuad } from './scene/graphics/render-pass-shader-quad.js'; +export { RenderPassRadixSort } from './scene/graphics/render-pass-radix-sort.js'; export { reprojectTexture } from './scene/graphics/reproject-texture.js'; // SCENE / MATERIALS diff --git a/src/platform/graphics/draw-commands.js b/src/platform/graphics/draw-commands.js index a15c9cec620..957385537d2 100644 --- a/src/platform/graphics/draw-commands.js +++ b/src/platform/graphics/draw-commands.js @@ -75,6 +75,14 @@ class DrawCommands { */ slotIndex = 0; + /** + * Total number of primitives across all sub-draws (pre-calculated). + * + * @type {number} + * @ignore + */ + primitiveCount = 0; + /** * @param {import('./graphics-device.js').GraphicsDevice} device - The graphics device. * @param {number} [indexSizeBytes] - Size of index in bytes for WebGL multi-draw (1, 2 or 4). @@ -127,7 +135,7 @@ class DrawCommands { */ update(count) { this._count = count; - this.impl.update?.(count); + this.primitiveCount = this.impl.update?.(count) ?? 0; } } diff --git a/src/platform/graphics/gpu-profiler.js b/src/platform/graphics/gpu-profiler.js index 691bf507637..bce553a3eb7 100644 --- a/src/platform/graphics/gpu-profiler.js +++ b/src/platform/graphics/gpu-profiler.js @@ -45,6 +45,22 @@ class GpuProfiler { */ _frameTime = 0; + /** + * Per-pass timing data, with accumulated timings for passes with the same name. + * + * @type {Map} + * @private + */ + _passTimings = new Map(); + + /** + * Cache for parsed pass names to avoid repeated string operations. + * + * @type {Map} + * @private + */ + _nameCache = new Map(); + /** * The maximum number of slots that can be allocated during the frame. * @@ -69,6 +85,16 @@ class GpuProfiler { return this._enableRequest; } + /** + * Get the per-pass timing data. + * + * @type {Map} + * @ignore + */ + get passTimings() { + return this._passTimings; + } + processEnableRequest() { if (this._enableRequest !== this._enabled) { this._enabled = this._enableRequest; @@ -83,10 +109,36 @@ class GpuProfiler { this.frameAllocations = []; } + /** + * Parse a render pass name to a simplified form for stats. + * Uses a cache to avoid repeated string operations. + * + * @param {string} name - The original pass name (e.g., "RenderPassCompose"). + * @returns {string} The parsed name (e.g., "compose"). + * @private + */ + _parsePassName(name) { + // check cache first + let parsedName = this._nameCache.get(name); + if (parsedName === undefined) { + // remove "RenderPass" prefix if present + if (name.startsWith('RenderPass')) { + parsedName = name.substring(10); + } else { + parsedName = name; + } + this._nameCache.set(name, parsedName); + } + return parsedName; + } + report(renderVersion, timings) { if (timings) { const allocations = this.pastFrameAllocations.get(renderVersion); + if (!allocations) { + return; + } Debug.assert(allocations.length === timings.length); // store frame duration @@ -94,6 +146,19 @@ class GpuProfiler { this._frameTime = timings.reduce((sum, t) => sum + t, 0); } + // clear old pass timings + this._passTimings.clear(); + + // accumulate per-pass timings + for (let i = 0; i < allocations.length; ++i) { + const name = allocations[i]; + const timing = timings[i]; + const parsedName = this._parsePassName(name); + + // accumulate timings for passes with the same name + this._passTimings.set(parsedName, (this._passTimings.get(parsedName) || 0) + timing); + } + // log out timings if (Tracing.get(TRACEID_GPU_TIMINGS)) { Debug.trace(TRACEID_GPU_TIMINGS, `-- GPU timings for frame ${renderVersion} --`); diff --git a/src/platform/graphics/graphics-device.js b/src/platform/graphics/graphics-device.js index 30ca3052a9d..fd85aa88bf2 100644 --- a/src/platform/graphics/graphics-device.js +++ b/src/platform/graphics/graphics-device.js @@ -545,6 +545,12 @@ class GraphicsDevice extends EventHandler { if (this.textureFloatFilterable) capsDefines.set('CAPS_TEXTURE_FLOAT_FILTERABLE', ''); if (this.textureFloatRenderable) capsDefines.set('CAPS_TEXTURE_FLOAT_RENDERABLE', ''); if (this.supportsMultiDraw) capsDefines.set('CAPS_MULTI_DRAW', ''); + + // Platform defines + if (platform.desktop) capsDefines.set('PLATFORM_DESKTOP', ''); + if (platform.mobile) capsDefines.set('PLATFORM_MOBILE', ''); + if (platform.android) capsDefines.set('PLATFORM_ANDROID', ''); + if (platform.ios) capsDefines.set('PLATFORM_IOS', ''); } /** diff --git a/src/platform/graphics/shader.js b/src/platform/graphics/shader.js index 55af0594f79..a20fe4f3012 100644 --- a/src/platform/graphics/shader.js +++ b/src/platform/graphics/shader.js @@ -72,6 +72,8 @@ class Shader { * useTransformFeedback or compute shader is specified. * @param {string} [definition.cshader] - Compute shader source (WGSL code). Only supported on * WebGPU platform. + * @param {string} [definition.computeEntryPoint] - The entry point function name for the compute + * shader. Defaults to 'main'. * @param {Map} [definition.vincludes] - A map containing key-value pairs of * include names and their content. These are used for resolving #include directives in the * vertex shader source. diff --git a/src/platform/graphics/upload-stream.js b/src/platform/graphics/upload-stream.js index 4762787444c..8b3cd58f186 100644 --- a/src/platform/graphics/upload-stream.js +++ b/src/platform/graphics/upload-stream.js @@ -2,6 +2,7 @@ * @import { GraphicsDevice } from './graphics-device.js' * @import { StorageBuffer } from './storage-buffer.js' * @import { Texture } from './texture.js' + * @import { EventHandle } from '../../core/event-handle.js' */ /** @@ -15,6 +16,14 @@ * @ignore */ class UploadStream { + /** + * Event handle for device lost event. + * + * @type {EventHandle|null} + * @protected + */ + _deviceLostEvent = null; + /** * Create a new UploadStream instance. * @@ -29,6 +38,21 @@ class UploadStream { // Create platform-specific implementation this.impl = device.createUploadStreamImpl(this); + + // Register device lost handler + this._deviceLostEvent = this.device.on('devicelost', this._onDeviceLost, this); + } + + /** + * Destroy the upload stream and clean up all pooled resources. + */ + destroy() { + // Remove event listener + this._deviceLostEvent?.off(); + this._deviceLostEvent = null; + + this.impl?.destroy(); + this.impl = null; } /** @@ -53,11 +77,12 @@ class UploadStream { } /** - * Destroy the upload stream and clean up all pooled resources. + * Handles device lost event. Override in platform implementations. + * + * @private */ - destroy() { - this.impl?.destroy(); - this.impl = null; + _onDeviceLost() { + this.impl?._onDeviceLost?.(); } } diff --git a/src/platform/graphics/webgl/webgl-draw-commands.js b/src/platform/graphics/webgl/webgl-draw-commands.js index bc331e70ced..dbe2457454e 100644 --- a/src/platform/graphics/webgl/webgl-draw-commands.js +++ b/src/platform/graphics/webgl/webgl-draw-commands.js @@ -45,6 +45,28 @@ class WebglDrawCommands { this.glOffsetsBytes[i] = firstIndexOrVertex * this.indexSizeBytes; this.glInstanceCounts[i] = instanceCount; } + + /** + * Calculate total primitives for stats (profiler builds only). + * @param {number} count - Number of active draws. + * @returns {number} Total primitive count. + */ + update(count) { + // calculate total primitives for stats + let totalPrimitives = 0; + + // #if _PROFILER + if (this.glCounts && this.glInstanceCounts && count > 0) { + for (let d = 0; d < count; d++) { + const indexOrVertexCount = this.glCounts[d]; + const instanceCount = this.glInstanceCounts[d]; + totalPrimitives += indexOrVertexCount * instanceCount; + } + } + // #endif + + return totalPrimitives; + } } export { WebglDrawCommands }; diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index d78aa9bbf0d..0e0c91e51fb 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -1887,7 +1887,13 @@ class WebglGraphicsDevice extends GraphicsDevice { this._drawCallsPerFrame++; // #if _PROFILER - this._primsPerFrame[primitive.type] += primitive.count * (numInstances > 1 ? numInstances : 1); + if (drawCommands) { + // use pre-calculated primitive count from drawCommands + this._primsPerFrame[primitive.type] += drawCommands.primitiveCount; + } else { + // single draw + this._primsPerFrame[primitive.type] += primitive.count * (numInstances > 1 ? numInstances : 1); + } // #endif } } @@ -2088,6 +2094,7 @@ class WebglGraphicsDevice extends GraphicsDevice { this.setRenderTarget(renderTarget); this.initRenderTarget(renderTarget); + this.setFramebuffer(renderTarget.impl._glFrameBuffer); return new Promise((resolve, reject) => { this.readPixelsAsync(x, y, width, height, data).then((data) => { diff --git a/src/platform/graphics/webgl/webgl-upload-stream.js b/src/platform/graphics/webgl/webgl-upload-stream.js index eedb93fc18e..66b582c7d1c 100644 --- a/src/platform/graphics/webgl/webgl-upload-stream.js +++ b/src/platform/graphics/webgl/webgl-upload-stream.js @@ -44,6 +44,17 @@ class WebglUploadStream { }); } + /** + * Handles device lost event by clearing all PBO and sync object arrays. + * + * @protected + */ + _onDeviceLost() { + // Clear arrays without trying to delete objects (context is already lost) + this.availablePBOs.length = 0; + this.pendingPBOs.length = 0; + } + /** * Update PBOs: poll completed ones and remove undersized buffers. * @@ -153,6 +164,8 @@ class WebglUploadStream { // Ensure texture is created and bound // @ts-ignore - setTexture is available on WebglGraphicsDevice device.setTexture(target, 0); + device.activeTexture(0); + device.bindTexture(target); // Rebind PBO for texSubImage2D gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pboInfo.pbo); @@ -160,7 +173,9 @@ class WebglUploadStream { // Set pixel-store parameters (use device methods for cached state) device.setUnpackFlipY(false); device.setUnpackPremultiplyAlpha(false); - gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + + // Use alignment matching the data's byte size (1, 2, 4, or 8) + gl.pixelStorei(gl.UNPACK_ALIGNMENT, data.BYTES_PER_ELEMENT); gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0); gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0); gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0); diff --git a/src/platform/graphics/webgpu/webgpu-draw-commands.js b/src/platform/graphics/webgpu/webgpu-draw-commands.js index 10112bd16f0..cf757c3e450 100644 --- a/src/platform/graphics/webgpu/webgpu-draw-commands.js +++ b/src/platform/graphics/webgpu/webgpu-draw-commands.js @@ -63,12 +63,29 @@ class WebgpuDrawCommands { /** * Upload AoS data to storage buffer. * @param {number} count - Number of active draws. + * @returns {number} Total primitive count. */ update(count) { if (this.storage && count > 0) { const used = count * 5; // 5 uints per draw this.storage.write(0, this.gpuIndirect, 0, used); } + + // calculate total primitives for stats + let totalPrimitives = 0; + + // #if _PROFILER + if (this.gpuIndirect && count > 0) { + for (let d = 0; d < count; d++) { + const offset = d * 5; + const indexOrVertexCount = this.gpuIndirect[offset + 0]; + const instanceCount = this.gpuIndirect[offset + 1]; + totalPrimitives += indexOrVertexCount * instanceCount; + } + } + // #endif + + return totalPrimitives; } destroy() { diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index 1d07aac436c..571d64e7437 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -693,6 +693,21 @@ class WebgpuGraphicsDevice extends GraphicsDevice { } } + // track draw calls - always count as 1 (one material setup, one API call) + this._drawCallsPerFrame++; + + // #if _PROFILER + // track primitive count + if (drawCommands) { + // use pre-calculated primitive count from drawCommands + this._primsPerFrame[primitive.type] += drawCommands.primitiveCount; + } else { + // single draw + const primCount = primitive.count * (numInstances > 1 ? numInstances : 1); + this._primsPerFrame[primitive.type] += primCount; + } + // #endif + WebgpuDebug.end(this, 'Drawing', { vb0, vb1, @@ -1256,6 +1271,10 @@ class WebgpuGraphicsDevice extends GraphicsDevice { return true; } + get hasTranspilers() { + return this.glslang && this.twgsl; + } + // #if _DEBUG pushMarker(name) { this.passEncoder?.pushDebugGroup(name); diff --git a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js index 0451d6c3811..6776b6bfe72 100644 --- a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js +++ b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js @@ -18,6 +18,14 @@ class WebgpuMipmapRenderer { /** @type {WebgpuGraphicsDevice} */ device; + /** + * Cache of render pipelines keyed by texture format. + * + * @type {Map} + * @private + */ + pipelineCache = new Map(); + constructor(device) { this.device = device; @@ -65,6 +73,7 @@ class WebgpuMipmapRenderer { destroy() { this.shader.destroy(); this.shader = null; + this.pipelineCache.clear(); } /** @@ -88,28 +97,34 @@ class WebgpuMipmapRenderer { const device = this.device; const wgpu = device.wgpu; - - /** @type {WebgpuShader} */ - const webgpuShader = this.shader.impl; - - const pipeline = wgpu.createRenderPipeline({ - layout: 'auto', - vertex: { - module: webgpuShader.getVertexShaderModule(), - entryPoint: webgpuShader.vertexEntryPoint - }, - fragment: { - module: webgpuShader.getFragmentShaderModule(), - entryPoint: webgpuShader.fragmentEntryPoint, - targets: [{ - format: textureDescr.format // use the same format as the texture - }] - }, - primitive: { - topology: 'triangle-strip' - } - }); - DebugHelper.setLabel(pipeline, 'RenderPipeline-MipmapRenderer'); + const format = textureDescr.format; + + // Get or create cached pipeline for this texture format + let pipeline = this.pipelineCache.get(format); + if (!pipeline) { + /** @type {WebgpuShader} */ + const webgpuShader = this.shader.impl; + + pipeline = wgpu.createRenderPipeline({ + layout: 'auto', + vertex: { + module: webgpuShader.getVertexShaderModule(), + entryPoint: webgpuShader.vertexEntryPoint + }, + fragment: { + module: webgpuShader.getFragmentShaderModule(), + entryPoint: webgpuShader.fragmentEntryPoint, + targets: [{ + format: format + }] + }, + primitive: { + topology: 'triangle-strip' + } + }); + DebugHelper.setLabel(pipeline, `RenderPipeline-MipmapRenderer-${format}`); + this.pipelineCache.set(format, pipeline); + } const texture = webgpuTexture.texture; const numFaces = texture.cubemap ? 6 : (texture.array ? texture.arrayLength : 1); diff --git a/src/platform/graphics/webgpu/webgpu-shader.js b/src/platform/graphics/webgpu/webgpu-shader.js index 5346b930c48..7628460fd42 100644 --- a/src/platform/graphics/webgpu/webgpu-shader.js +++ b/src/platform/graphics/webgpu/webgpu-shader.js @@ -69,6 +69,9 @@ class WebgpuShader { this._computeCode = definition.cshader ?? null; this.computeUniformBufferFormats = definition.computeUniformBufferFormats; this.computeBindGroupFormat = definition.computeBindGroupFormat; + if (definition.computeEntryPoint) { + this.computeEntryPoint = definition.computeEntryPoint; + } } else { diff --git a/src/platform/graphics/webgpu/webgpu-texture.js b/src/platform/graphics/webgpu/webgpu-texture.js index 3ab79292835..7a4ccedda32 100644 --- a/src/platform/graphics/webgpu/webgpu-texture.js +++ b/src/platform/graphics/webgpu/webgpu-texture.js @@ -2,7 +2,7 @@ import { TRACEID_RENDER_QUEUE } from '../../../core/constants.js'; import { Debug, DebugHelper } from '../../../core/debug.js'; import { math } from '../../../core/math/math.js'; import { - pixelFormatInfo, isCompressedPixelFormat, + pixelFormatInfo, isCompressedPixelFormat, getPixelFormatArrayType, ADDRESS_REPEAT, ADDRESS_CLAMP_TO_EDGE, ADDRESS_MIRRORED_REPEAT, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F, PIXELFORMAT_DEPTHSTENCIL, SAMPLETYPE_UNFILTERABLE_FLOAT, SAMPLETYPE_DEPTH, @@ -567,16 +567,20 @@ class WebgpuTexture { // async read data from the staging buffer to a temporary array return device.readBuffer(stagingBuffer, size, null, immediate).then((temp) => { + // determine target buffer - use user's data buffer or allocate new + const ArrayType = getPixelFormatArrayType(texture.format); + const targetBuffer = data?.buffer ?? new ArrayBuffer(height * bytesPerRow); + const target = new Uint8Array(targetBuffer, data?.byteOffset ?? 0, height * bytesPerRow); + // remove the 256 alignment padding from the end of each row - const target = (data?.constructor === Uint8Array) ? data : new Uint8Array(data?.buffer ?? height * bytesPerRow); for (let i = 0; i < height; i++) { const srcOffset = i * paddedBytesPerRow; const dstOffset = i * bytesPerRow; - const sub = temp.subarray(srcOffset, srcOffset + bytesPerRow); - target.set(sub, dstOffset); + target.set(temp.subarray(srcOffset, srcOffset + bytesPerRow), dstOffset); } - return data ?? target; + // return user's data or create correctly-typed array view + return data ?? new ArrayType(targetBuffer); }); } } diff --git a/src/platform/graphics/webgpu/webgpu-upload-stream.js b/src/platform/graphics/webgpu/webgpu-upload-stream.js index 8b8bb3c89b0..5c8e5a03ab5 100644 --- a/src/platform/graphics/webgpu/webgpu-upload-stream.js +++ b/src/platform/graphics/webgpu/webgpu-upload-stream.js @@ -39,6 +39,16 @@ class WebgpuUploadStream { this.useSingleBuffer = uploadStream.useSingleBuffer; } + /** + * Handles device lost event. + * TODO: Implement proper WebGPU device lost handling if needed. + * + * @protected + */ + _onDeviceLost() { + // WebGPU device lost handling not yet implemented + } + destroy() { this._destroyed = true; this.availableStagingBuffers.forEach(buffer => buffer.destroy()); diff --git a/src/scene/camera-shader-params.js b/src/scene/camera-shader-params.js index e7dbf729ee6..30c573371a1 100644 --- a/src/scene/camera-shader-params.js +++ b/src/scene/camera-shader-params.js @@ -66,7 +66,8 @@ class CameraShaderParams { this._definesDirty = false; defines.clear(); - if (this._sceneDepthMapLinear) defines.set('SCENE_DEPTHMAP_LINEAR', true); + if (this._sceneDepthMapLinear) defines.set('SCENE_DEPTHMAP_LINEAR', ''); + if (this.shaderOutputGamma === GAMMA_SRGB) defines.set('SCENE_COLORMAP_GAMMA', ''); defines.set('FOG', this._fog.toUpperCase()); defines.set('TONEMAP', tonemapNames[this._toneMapping]); defines.set('GAMMA', gammaNames[this.shaderOutputGamma]); diff --git a/src/scene/constants.js b/src/scene/constants.js index 9a8bbdc791b..b778d68031f 100644 --- a/src/scene/constants.js +++ b/src/scene/constants.js @@ -812,6 +812,9 @@ export const SHADER_SHADOW = 2; // shader pass used by the Picker class to render mesh ID export const SHADER_PICK = 3; +// shader pass used by the Picker class to render mesh ID and depth +export const SHADER_DEPTH_PICK = 4; + /** * Shader that performs forward rendering. * @@ -1135,3 +1138,18 @@ export const EVENT_POSTCULL = 'postcull'; * @ignore */ export const EVENT_CULL_END = 'cull:end'; + +/** + * @ignore + */ +export const GSPLAT_FORWARD = 1; + +/** + * @ignore + */ +export const GSPLAT_SHADOW = 2; + +/** + * @ignore + */ +export const SHADOWCAMERA_NAME = 'pcShadowCamera'; diff --git a/src/scene/graphics/render-pass-radix-sort-count.js b/src/scene/graphics/render-pass-radix-sort-count.js new file mode 100644 index 00000000000..4ec62966d2e --- /dev/null +++ b/src/scene/graphics/render-pass-radix-sort-count.js @@ -0,0 +1,137 @@ +import { SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; +import { RenderPassShaderQuad } from './render-pass-shader-quad.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; +import { ShaderChunks } from '../shader-lib/shader-chunks.js'; +import glslRadixSortCountPS from '../shader-lib/glsl/chunks/radix-sort/radix-sort-count.js'; +import wgslRadixSortCountPS from '../shader-lib/wgsl/chunks/radix-sort/radix-sort-count.js'; + +/** + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + * @import { Texture } from '../../platform/graphics/texture.js' + */ + +/** + * Render pass that counts digit occurrences per group (Pass 0 of radix sort). + * Outputs to R32F prefix sums texture. + * + * Has two variants: + * - sourceLinear=true: First pass, reads from user's linear-layout texture + * - sourceLinear=false: Subsequent passes, reads from internal Morton-layout texture + * + * @category Graphics + * @ignore + */ +class RenderPassRadixSortCount extends RenderPassShaderQuad { + /** + * Whether this pass reads from linear-layout source texture (first pass). + * + * @type {boolean} + */ + sourceLinear = false; + + /** + * Bits per radix step (usually 4). + * + * @type {number} + */ + bitsPerStep = 0; + + /** + * Log2 of group size (usually 4 for 16 elements). + * + * @type {number} + */ + groupSize = 0; + + /** + * Current bit offset for this pass. + * + * @type {number} + */ + currentBit = 0; + + /** + * Dynamic params updated per frame. + * + * @type {{elementCount: number, imageElementsLog2: number}} + * @private + */ + _dynamicParams = { elementCount: 0, imageElementsLog2: 0 }; + + /** + * @param {GraphicsDevice} device - The graphics device. + * @param {boolean} sourceLinear - Whether to read from linear-layout source texture. + * @param {number} bitsPerStep - Bits per radix step (usually 4). + * @param {number} groupSize - Log2 of group size (usually 4 for 16 elements). + * @param {number} currentBit - Current bit offset for this pass. + */ + constructor(device, sourceLinear, bitsPerStep, groupSize, currentBit) { + super(device); + + this.sourceLinear = sourceLinear; + this.bitsPerStep = bitsPerStep; + this.groupSize = groupSize; + this.currentBit = currentBit; + + // Register shader chunks + ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set('radixSortCountPS', glslRadixSortCountPS); + ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set('radixSortCountPS', wgslRadixSortCountPS); + + const defines = new Map(); + if (sourceLinear) { + defines.set('SOURCE_LINEAR', ''); + } + + const shaderName = sourceLinear ? 'RadixSortCountShaderLinear' : 'RadixSortCountShader'; + + this.shader = ShaderUtils.createShader(device, { + uniqueName: shaderName, + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentChunk: 'radixSortCountPS', + fragmentDefines: defines, + fragmentOutputTypes: 'float' + }); + + // Resolve uniform locations + this.keysTextureId = device.scope.resolve('keysTexture'); + this.bitsPerStepId = device.scope.resolve('bitsPerStep'); + this.groupSizeId = device.scope.resolve('groupSize'); + this.elementCountId = device.scope.resolve('elementCount'); + this.imageElementsLog2Id = device.scope.resolve('imageElementsLog2'); + this.currentBitId = device.scope.resolve('currentBit'); + } + + /** + * Sets the keys texture to read from. + * + * @param {Texture} keysTexture - The keys texture (R32U). + */ + setKeysTexture(keysTexture) { + this._keysTexture = keysTexture; + } + + /** + * Sets dynamic parameters (called each frame). + * + * @param {number} elementCount - Number of elements to sort. + * @param {number} imageElementsLog2 - Log2 of total texture elements. + */ + setDynamicParams(elementCount, imageElementsLog2) { + this._dynamicParams.elementCount = elementCount; + this._dynamicParams.imageElementsLog2 = imageElementsLog2; + } + + execute() { + this.keysTextureId.setValue(this._keysTexture); + this.bitsPerStepId.setValue(this.bitsPerStep); + this.groupSizeId.setValue(this.groupSize); + this.elementCountId.setValue(this._dynamicParams.elementCount); + this.imageElementsLog2Id.setValue(this._dynamicParams.imageElementsLog2); + this.currentBitId.setValue(this.currentBit); + + super.execute(); + } +} + +export { RenderPassRadixSortCount }; diff --git a/src/scene/graphics/render-pass-radix-sort-reorder.js b/src/scene/graphics/render-pass-radix-sort-reorder.js new file mode 100644 index 00000000000..f97b0b112fa --- /dev/null +++ b/src/scene/graphics/render-pass-radix-sort-reorder.js @@ -0,0 +1,185 @@ +import { SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; +import { RenderPassShaderQuad } from './render-pass-shader-quad.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; +import { ShaderChunks } from '../shader-lib/shader-chunks.js'; +import glslRadixSortReorderPS from '../shader-lib/glsl/chunks/radix-sort/radix-sort-reorder.js'; +import wgslRadixSortReorderPS from '../shader-lib/wgsl/chunks/radix-sort/radix-sort-reorder.js'; + +/** + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + * @import { Texture } from '../../platform/graphics/texture.js' + */ + +/** + * Render pass that reorders elements using binary search through mipmap hierarchy + * (Pass 1 of radix sort). Uses MRT to output both keys (R32U) and indices (R32U). + * + * Has multiple variants: + * - sourceLinear=true: First pass, reads keys from user's linear-layout texture + * - sourceLinear=false: Subsequent passes, reads keys from internal Morton-layout texture + * - outputLinear=true: Outputs indices in linear layout (simpler for consumers) + * + * @category Graphics + * @ignore + */ +class RenderPassRadixSortReorder extends RenderPassShaderQuad { + /** + * Whether this pass reads from linear-layout source texture (first pass). + * + * @type {boolean} + */ + sourceLinear = false; + + /** + * Whether to output indices in linear layout. + * + * @type {boolean} + */ + outputLinear = false; + + /** + * Bits per radix step (usually 4). + * + * @type {number} + */ + bitsPerStep = 0; + + /** + * Log2 of group size (usually 4 for 16 elements). + * + * @type {number} + */ + groupSize = 0; + + /** + * Current bit offset for this pass. + * + * @type {number} + */ + currentBit = 0; + + /** + * Dynamic params updated per frame. + * + * @type {{elementCount: number, imageElementsLog2: number, imageSize: number}} + * @private + */ + _dynamicParams = { elementCount: 0, imageElementsLog2: 0, imageSize: 0 }; + + /** + * @param {GraphicsDevice} device - The graphics device. + * @param {boolean} sourceLinear - Whether to read from linear-layout source texture. + * @param {boolean} outputLinear - Whether to output indices in linear layout. + * @param {number} bitsPerStep - Bits per radix step (usually 4). + * @param {number} groupSize - Log2 of group size (usually 4 for 16 elements). + * @param {number} currentBit - Current bit offset for this pass. + */ + constructor(device, sourceLinear, outputLinear, bitsPerStep, groupSize, currentBit) { + super(device); + + this.sourceLinear = sourceLinear; + this.outputLinear = outputLinear; + this.bitsPerStep = bitsPerStep; + this.groupSize = groupSize; + this.currentBit = currentBit; + + // Register shader chunks + ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set('radixSortReorderPS', glslRadixSortReorderPS); + ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set('radixSortReorderPS', wgslRadixSortReorderPS); + + const defines = new Map(); + if (sourceLinear) { + defines.set('SOURCE_LINEAR', ''); + } + if (outputLinear) { + defines.set('OUTPUT_LINEAR', ''); + } + + let shaderName = 'RadixSortReorderShader'; + if (sourceLinear) shaderName += 'SourceLinear'; + if (outputLinear) shaderName += 'OutputLinear'; + + this.shader = ShaderUtils.createShader(device, { + uniqueName: shaderName, + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentChunk: 'radixSortReorderPS', + fragmentDefines: defines, + fragmentOutputTypes: ['uvec4', 'uvec4'] // MRT: keys (uint) and indices (uint) + }); + + // Resolve uniform locations + this.keysTextureId = device.scope.resolve('keysTexture'); + if (!sourceLinear) { + // Non-first passes need indices texture + this.indicesTextureId = device.scope.resolve('indicesTexture'); + } + this.prefixSumsId = device.scope.resolve('prefixSums'); + this.bitsPerStepId = device.scope.resolve('bitsPerStep'); + this.groupSizeId = device.scope.resolve('groupSize'); + this.elementCountId = device.scope.resolve('elementCount'); + this.imageElementsLog2Id = device.scope.resolve('imageElementsLog2'); + this.currentBitId = device.scope.resolve('currentBit'); + this.imageSizeId = device.scope.resolve('imageSize'); + } + + /** + * Sets the keys texture to read from. + * + * @param {Texture} keysTexture - The keys texture (R32U). + */ + setKeysTexture(keysTexture) { + this._keysTexture = keysTexture; + } + + /** + * Sets the indices texture to read from. + * + * @param {Texture} indicesTexture - The indices texture (R32U). + */ + setIndicesTexture(indicesTexture) { + this._indicesTexture = indicesTexture; + } + + /** + * Sets the prefix sums texture. + * + * @param {Texture} prefixSums - The prefix sums texture (R32F with mipmaps). + */ + setPrefixSumsTexture(prefixSums) { + this._prefixSums = prefixSums; + } + + /** + * Sets dynamic parameters (called each frame). + * + * @param {number} elementCount - Number of elements to sort. + * @param {number} imageElementsLog2 - Log2 of total texture elements. + * @param {number} imageSize - Size of the internal texture (power of 2). + */ + setDynamicParams(elementCount, imageElementsLog2, imageSize) { + this._dynamicParams.elementCount = elementCount; + this._dynamicParams.imageElementsLog2 = imageElementsLog2; + this._dynamicParams.imageSize = imageSize; + } + + execute() { + this.keysTextureId.setValue(this._keysTexture); + if (!this.sourceLinear) { + this.indicesTextureId.setValue(this._indicesTexture); + } + + this.prefixSumsId.setValue(this._prefixSums); + + this.bitsPerStepId.setValue(this.bitsPerStep); + this.groupSizeId.setValue(this.groupSize); + this.elementCountId.setValue(this._dynamicParams.elementCount); + this.imageElementsLog2Id.setValue(this._dynamicParams.imageElementsLog2); + this.currentBitId.setValue(this.currentBit); + this.imageSizeId.setValue(this._dynamicParams.imageSize); + + super.execute(); + } +} + +export { RenderPassRadixSortReorder }; diff --git a/src/scene/graphics/render-pass-radix-sort.js b/src/scene/graphics/render-pass-radix-sort.js new file mode 100644 index 00000000000..f4781cc9b0d --- /dev/null +++ b/src/scene/graphics/render-pass-radix-sort.js @@ -0,0 +1,491 @@ +import { Debug } from '../../core/debug.js'; +import { Color } from '../../core/math/color.js'; +import { Texture } from '../../platform/graphics/texture.js'; +import { RenderTarget } from '../../platform/graphics/render-target.js'; +import { RenderPass } from '../../platform/graphics/render-pass.js'; +import { + ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, FILTER_NEAREST_MIPMAP_NEAREST, + PIXELFORMAT_R32F, PIXELFORMAT_R32U +} from '../../platform/graphics/constants.js'; + +import { RenderPassRadixSortCount } from './render-pass-radix-sort-count.js'; +import { RenderPassRadixSortReorder } from './render-pass-radix-sort-reorder.js'; + +/** + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + */ + +// Constants for radix sort +const BITS_PER_STEP = 4; // 4-bit radix (16 buckets) +const GROUP_SIZE = 4; // Log2 of 16 (16 elements per group) + +/** + * A render pass that performs GPU-based radix sort using mipmap-based prefix sums. + * + * This implementation is based on: + * - VRChat Gaussian Splatting by MichaelMoroz: https://github.com/MichaelMoroz/VRChatGaussianSplatting + * - Mipmap prefix sum trick by d4rkpl4y3r: https://github.com/d4rkc0d3r/CompactSparseTextureDemo + * + * ## Algorithm Overview + * + * The sort uses a 4-bit radix (16 buckets) and processes keys in multiple passes, + * one pass per 4-bit chunk. Each pass consists of: + * + * 1. **Count Pass**: For each digit (0-15), count how many keys in each group have that digit. + * Output is an R32F texture where each pixel stores a count. Groups are 16 elements. + * + * 2. **Mipmap Generation**: Generate mipmaps for the count texture using hardware mipmap + * generation. This creates a quadtree of counts that enables efficient binary search. + * + * 3. **Reorder Pass**: For each output position, binary search through the mipmap hierarchy + * to find which source element maps to it. The mipmap structure enables O(log N) lookup + * per element instead of O(N) linear scan. + * + * ## Mipmap Prefix Sum Trick + * + * The key insight is that mipmaps naturally form a quadtree of averages. By writing counts + * (e.g., 1.0 for active pixels) into an R32F texture with auto-generated mipmaps: + * + * - Each mip level stores the average of the 4 pixels below it + * - To reconstruct actual counts, multiply by 4^level (i.e., `1 << (level * 2)`) + * - This gives us a hierarchical prefix sum structure + * + * Binary search traversal: + * - Start at maxMipLevel and work down to level 0 + * - At each level, check 3 quadrants (can skip 4th - if not in first 3, must be in 4th) + * - Order: bottom-left → bottom-right → top-left → top-right (Z-order/Morton curve) + * - Accumulate prefix sums while descending to find the target element + * + * The Z-order traversal ensures stable sorting: if element A comes before B in the input, + * it remains before B in the output. + * + * ## Internal Data Layout + * + * - Internal keys/indices use Morton order (Z-order curve) for better texture cache locality + * - Source keys texture uses linear (row-major) layout + * - Output sorted indices use linear layout for simple consumer access + * + * ## Complexity + * + * - Time: O(N log N) per pass due to mipmap binary search + * - Passes: ceil(numBits / 4) passes for numBits-bit keys + * - Memory: 2x keys textures + 2x indices textures + 1x prefix sums texture (all power-of-2) + * + * @category Graphics + * @ignore + */ +class RenderPassRadixSort extends RenderPass { + /** + * The current sorted indices texture (R32U). Access sorted indices using Morton lookup. + * + * @type {Texture|null} + */ + _currentIndices = null; + + /** + * Current number of radix passes. + * + * @type {number} + */ + _numPasses = 0; + + /** + * Current internal texture size (power of 2). + * + * @type {number} + */ + _internalSize = 0; + + /** + * Internal keys texture 0 (ping-pong buffer). + * + * @type {Texture|null} + */ + _keys0 = null; + + /** + * Internal keys texture 1 (ping-pong buffer). + * + * @type {Texture|null} + */ + _keys1 = null; + + /** + * Internal indices texture 0 (ping-pong buffer). + * + * @type {Texture|null} + */ + _indices0 = null; + + /** + * Internal indices texture 1 (ping-pong buffer). + * + * @type {Texture|null} + */ + _indices1 = null; + + /** + * Prefix sums texture (R32F with mipmaps). + * + * @type {Texture|null} + */ + _prefixSums = null; + + /** + * Sort render target 0 (MRT for keys + indices). + * + * @type {RenderTarget|null} + */ + _sortRT0 = null; + + /** + * Sort render target 1 (MRT for keys + indices). + * + * @type {RenderTarget|null} + */ + _sortRT1 = null; + + /** + * Prefix sums render target. + * + * @type {RenderTarget|null} + */ + _prefixSumsRT = null; + + /** + * Count passes for each radix iteration. + * + * @type {RenderPassRadixSortCount[]} + */ + _countPasses = []; + + /** + * Reorder passes for each radix iteration. + * + * @type {RenderPassRadixSortReorder[]} + */ + _reorderPasses = []; + + /** + * Number of elements to sort (set by setup()). + * + * @type {number} + */ + _elementCount = 0; + + /** + * The source keys texture (set by setup()). + * + * @type {Texture|null} + */ + _keysTexture = null; + + /** + * Creates a new RenderPassRadixSort instance. + * + * @param {GraphicsDevice} device - The graphics device. + */ + // eslint-disable-next-line no-useless-constructor + constructor(device) { + super(device); + } + + /** + * Gets the sorted indices texture (R32U, linear layout). Use `.width` for texture dimensions. + * Access with: `texelFetch(texture, ivec2(index % width, index / width), 0).r` + * + * @type {Texture|null} + */ + get sortedIndices() { + return this._currentIndices; + } + + /** + * Sets up the sort for the current frame. + * + * Note: The source keys texture is read-only and can be any size. + * The sorted indices will be in a separate power-of-2 texture. + * + * @param {Texture} keysTexture - R32U texture containing sort keys (linear layout, any size). + * @param {number} elementCount - Number of elements to sort. + * @param {number} [numBits] - Number of bits to sort (1-24). More bits = more passes. + */ + setup(keysTexture, elementCount, numBits = 16) { + Debug.assert(keysTexture, 'RenderPassRadixSort.setup: keysTexture is required'); + Debug.assert(elementCount > 0, 'RenderPassRadixSort.setup: elementCount must be > 0'); + Debug.assert(numBits >= 1 && numBits <= 24, 'RenderPassRadixSort.setup: numBits must be 1-24'); + + this._keysTexture = keysTexture; + this._elementCount = elementCount; + + // Check if number of passes changed - only recreate if needed + // (e.g., 11 and 12 bits both need 3 passes, so no recreation needed) + const numPasses = Math.ceil(numBits / BITS_PER_STEP); + if (numPasses !== this._numPasses) { + this._destroyPasses(); + this._numPasses = numPasses; + } + + // Calculate required internal texture size (power of 2) + const requiredSize = this._calculateInternalSize(elementCount); + if (requiredSize !== this._internalSize) { + // Need to destroy passes first since they reference old render targets + this._destroyPasses(); + this._resizeInternalTextures(requiredSize); + this._internalSize = requiredSize; + } + + // Create passes if needed + if (this._countPasses.length === 0) { + this._createPasses(); + } + } + + /** + * Calculates the required power-of-2 texture size for the given element count. + * + * @param {number} elementCount - Number of elements. + * @returns {number} Power-of-2 size. + * @private + */ + _calculateInternalSize(elementCount) { + // Need square power-of-2 texture that can hold elementCount elements + const side = Math.ceil(Math.sqrt(elementCount)); + return Math.pow(2, Math.ceil(Math.log2(side))); + } + + /** + * Creates or resizes internal textures. + * + * @param {number} size - Power-of-2 size for textures. + * @private + */ + _resizeInternalTextures(size) { + + // Destroy old textures + this._destroyInternalTextures(); + + // Keys textures (R32U, Morton layout) + this._keys0 = this._createTexture('RadixSortKeys0', size, PIXELFORMAT_R32U); + this._keys1 = this._createTexture('RadixSortKeys1', size, PIXELFORMAT_R32U); + + // Indices textures (R32U, Morton layout) + this._indices0 = this._createTexture('RadixSortIndices0', size, PIXELFORMAT_R32U); + this._indices1 = this._createTexture('RadixSortIndices1', size, PIXELFORMAT_R32U); + + // Prefix sums texture (R32F with mipmaps) + // This texture has one pixel per (digit, group) combination: + // - With 4-bit radix: 16 possible digit values (0-15) + // - With group size 16: numGroups = size² / 16 + // - Total pixels needed: 16 digits × (size² / 16) groups = size² pixels + // General formula: size * 2^(bitsPerStep/2) / 2^(groupSize/2) + // With bitsPerStep=4, groupSize=4: size * 4 / 4 = size (same as keys texture) + // Note: With current constants, prefixSize === size. The formula is kept general + // in case we ever change to a different radix (e.g., 8-bit would need larger texture). + const prefixSize = size * Math.pow(2, BITS_PER_STEP / 2) / Math.pow(2, GROUP_SIZE / 2); + this._prefixSums = this._createTexture('RadixSortPrefixSums', prefixSize, PIXELFORMAT_R32F, true); + + // Create MRT render targets (keys + indices) + this._sortRT0 = new RenderTarget({ + name: 'RadixSortRT0', + colorBuffers: [this._keys0, this._indices0], + depth: false + }); + + this._sortRT1 = new RenderTarget({ + name: 'RadixSortRT1', + colorBuffers: [this._keys1, this._indices1], + depth: false + }); + + // Render target for prefix sums + this._prefixSumsRT = new RenderTarget({ + name: 'RadixSortPrefixSumsRT', + colorBuffer: this._prefixSums, + depth: false + }); + } + + /** + * Creates a texture for radix sort. + * + * @param {string} name - Texture name. + * @param {number} size - Texture size. + * @param {number} format - Pixel format (PIXELFORMAT_R32U or PIXELFORMAT_R32F). + * @param {boolean} [mipmaps] - Whether to generate mipmaps. Defaults to false. + * @returns {Texture} The created texture. + * @private + */ + _createTexture(name, size, format, mipmaps = false) { + return new Texture(this.device, { + name: name, + width: size, + height: size, + format: format, + mipmaps: mipmaps, + minFilter: mipmaps ? FILTER_NEAREST_MIPMAP_NEAREST : FILTER_NEAREST, + magFilter: FILTER_NEAREST, + addressU: ADDRESS_CLAMP_TO_EDGE, + addressV: ADDRESS_CLAMP_TO_EDGE + }); + } + + /** + * Destroys internal textures and render targets. + * + * @private + */ + _destroyInternalTextures() { + this._sortRT0?.destroy(); + this._sortRT1?.destroy(); + this._prefixSumsRT?.destroy(); + + this._keys0?.destroy(); + this._keys1?.destroy(); + this._indices0?.destroy(); + this._indices1?.destroy(); + this._prefixSums?.destroy(); + + this._sortRT0 = null; + this._sortRT1 = null; + this._prefixSumsRT = null; + this._keys0 = null; + this._keys1 = null; + this._indices0 = null; + this._indices1 = null; + this._prefixSums = null; + } + + /** + * Creates the sort passes based on numBits. + * Sets up beforePasses with the complete pass sequence (count, mipmap, reorder for each iteration). + * + * @private + */ + _createPasses() { + const device = this.device; + const numPasses = this._numPasses; + + // Ping-pong state for render target assignment (deterministic) + let nextRT = this._sortRT1; + + // Create count, mipmap, and reorder passes in order + for (let i = 0; i < numPasses; i++) { + const sourceLinear = (i === 0); + const outputLinear = (i === numPasses - 1); + + const currentBit = i * BITS_PER_STEP; + + // Count pass - renders to R32F prefix sums texture (mipmaps auto-generated after render) + const countPass = new RenderPassRadixSortCount(device, sourceLinear, BITS_PER_STEP, GROUP_SIZE, currentBit); + countPass.init(this._prefixSumsRT); + countPass.setClearColor(new Color(0, 0, 0, 0)); + this._countPasses.push(countPass); + this.beforePasses.push(countPass); + + // Reorder pass - renders to R32U keys/indices textures + // Last pass outputs linear layout for simpler consumer access + const reorderPass = new RenderPassRadixSortReorder(device, sourceLinear, outputLinear, BITS_PER_STEP, GROUP_SIZE, currentBit); + reorderPass.setPrefixSumsTexture(this._prefixSums); + reorderPass.init(nextRT); + this._reorderPasses.push(reorderPass); + this.beforePasses.push(reorderPass); + + // Swap RT for next iteration + nextRT = (nextRT === this._sortRT1) ? this._sortRT0 : this._sortRT1; + } + + // Determine which indices texture will contain the final result + // After numPasses swaps: odd = _indices1, even = _indices0 + this._currentIndices = (numPasses % 2 === 1) ? this._indices1 : this._indices0; + } + + /** + * Destroys all sort passes. + * + * @private + */ + _destroyPasses() { + // Destroy all passes in beforePasses (includes count and reorder passes) + for (const pass of this.beforePasses) { + pass.destroy(); + } + this.beforePasses.length = 0; + this._countPasses.length = 0; + this._reorderPasses.length = 0; + } + + frameUpdate() { + super.frameUpdate(); + + if (!this._keysTexture || this._countPasses.length === 0) { + return; + } + + const numPasses = this._countPasses.length; + + // Calculate dynamic params for this frame + const elementCount = this._elementCount; + const imageElementsLog2 = Math.log2(this._internalSize * this._internalSize); + const imageSize = this._internalSize; + + // Ping-pong state for texture assignment + let currentKeys = this._keys0; + let currentIndices = this._indices0; + + // Update dynamic properties for each pass (pass sequence is already set up in _createPasses) + for (let i = 0; i < numPasses; i++) { + const sourceLinear = (i === 0); + const countPass = this._countPasses[i]; + const reorderPass = this._reorderPasses[i]; + + // Configure count pass textures and dynamic params + if (sourceLinear) { + countPass.setKeysTexture(this._keysTexture); + } else { + countPass.setKeysTexture(currentKeys); + } + countPass.setDynamicParams(elementCount, imageElementsLog2); + + // Configure reorder pass textures and dynamic params + if (sourceLinear) { + reorderPass.setKeysTexture(this._keysTexture); + // First pass doesn't need indices texture (implicitly [0,1,2,...]) + } else { + reorderPass.setKeysTexture(currentKeys); + reorderPass.setIndicesTexture(currentIndices); + } + reorderPass.setDynamicParams(elementCount, imageElementsLog2, imageSize); + + // Swap ping-pong buffers for next iteration + currentKeys = (currentKeys === this._keys0) ? this._keys1 : this._keys0; + currentIndices = (currentIndices === this._indices0) ? this._indices1 : this._indices0; + } + } + + /** + * Executes the GPU radix sort. This is a convenience method that combines setup, frameUpdate, + * and rendering all passes in one call. + * + * @param {Texture} keysTexture - R32U texture containing sort keys (linear layout, any size). + * @param {number} elementCount - Number of elements to sort. + * @param {number} [numBits] - Number of bits to sort (1-24). More bits = more passes. Defaults to 16. + * @returns {Texture} The sorted indices texture (R32U, linear layout). + */ + sort(keysTexture, elementCount, numBits = 16) { + this.setup(keysTexture, elementCount, numBits); + this.frameUpdate(); + for (const pass of this.beforePasses) { + pass.render(); + } + return this.sortedIndices; + } + + destroy() { + this._destroyPasses(); + this._destroyInternalTextures(); + super.destroy(); + } +} + +export { RenderPassRadixSort }; diff --git a/src/scene/graphics/render-pass-shader-quad.js b/src/scene/graphics/render-pass-shader-quad.js index 4582199cab0..3ffc45bee0c 100644 --- a/src/scene/graphics/render-pass-shader-quad.js +++ b/src/scene/graphics/render-pass-shader-quad.js @@ -7,6 +7,7 @@ import { RenderPass } from '../../platform/graphics/render-pass.js'; /** * @import { Shader } from '../../platform/graphics/shader.js' * @import { StencilParameters } from '../../platform/graphics/stencil-parameters.js' + * @import { Vec4 } from '../../core/math/vec4.js' */ /** @@ -60,6 +61,22 @@ class RenderPassShaderQuad extends RenderPass { */ stencilBack = null; + /** + * Optional viewport rectangle (x, y, width, height). If set, the quad renders only to this + * region and the original viewport is restored after rendering. + * + * @type {Vec4|undefined} + */ + viewport; + + /** + * Optional scissor rectangle (x, y, width, height). If set, pixels outside this region are + * discarded. Only used when viewport is also set. Defaults to the viewport if not specified. + * + * @type {Vec4|undefined} + */ + scissor; + /** * Sets the shader used to render the quad. * @@ -92,7 +109,7 @@ class RenderPassShaderQuad extends RenderPass { device.setDepthState(this.depthState); device.setStencilState(this.stencilFront, this.stencilBack); - this.quadRender.render(); + this.quadRender?.render(this.viewport, this.scissor); } } diff --git a/src/scene/gsplat-unified/gsplat-asset-loader-base.js b/src/scene/gsplat-unified/gsplat-asset-loader-base.js index 6ff9c4eb1c3..e2a029053c4 100644 --- a/src/scene/gsplat-unified/gsplat-asset-loader-base.js +++ b/src/scene/gsplat-unified/gsplat-asset-loader-base.js @@ -5,13 +5,9 @@ import { Debug } from '../../core/debug.js'; * GSplat asset loaders must implement. * * @category Asset + * @ignore */ class GSplatAssetLoaderBase { - /** - * Number of ticks to wait before unloading a zero-ref file. - */ - cooldownTicks = 100; - /** * Initiates loading of a gsplat asset. This is a fire-and-forget operation that starts * the loading process. @@ -43,6 +39,15 @@ class GSplatAssetLoaderBase { getResource(url) { Debug.error('GSplatAssetLoaderBase#getResource: Not implemented'); } + + /** + * Destroys the loader and cleans up any resources it holds. + * + * @abstract + */ + destroy() { + // Base implementation does nothing - subclasses should override if cleanup is needed + } } export { GSplatAssetLoaderBase }; diff --git a/src/scene/gsplat-unified/gsplat-director.js b/src/scene/gsplat-unified/gsplat-director.js index 4460c4e776b..16b0b46e357 100644 --- a/src/scene/gsplat-unified/gsplat-director.js +++ b/src/scene/gsplat-unified/gsplat-director.js @@ -1,4 +1,6 @@ import { GSplatManager } from './gsplat-manager.js'; +import { SetUtils } from '../../core/set-utils.js'; +import { GSPLAT_FORWARD, GSPLAT_SHADOW } from '../constants.js'; /** * @import { LayerComposition } from '../composition/layer-composition.js' @@ -6,12 +8,13 @@ import { GSplatManager } from './gsplat-manager.js'; * @import { Layer } from '../layer.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { GraphNode } from '../graph-node.js' - * @import { GSplatAssetLoaderBase } from './gsplat-asset-loader-base.js' * @import { Scene } from '../scene.js' * @import { Renderer } from '../renderer/renderer.js' * @import { EventHandler } from '../../core/event-handler.js' */ +const tempLayersToRemove = []; + /** * Per layer data the director keeps track of. * @@ -19,22 +22,105 @@ import { GSplatManager } from './gsplat-manager.js'; */ class GSplatLayerData { /** - * @type {GSplatManager} + * @type {GSplatManager|null} + */ + gsplatManager = null; + + /** + * @type {GSplatManager|null} + */ + gsplatManagerShadow = null; + + /** + * @param {GraphicsDevice} device - The graphics device. + * @param {GSplatDirector} director - The director. + * @param {Layer} layer - The layer. + * @param {Camera} camera - The camera. */ - gsplatManager; + constructor(device, director, layer, camera) { + this.updateConfiguration(device, director, layer, camera); + } /** + * Creates a new GSplatManager, sets its render mode, and fires the material:created event. + * * @param {GraphicsDevice} device - The graphics device. * @param {GSplatDirector} director - The director. * @param {Layer} layer - The layer. * @param {GraphNode} cameraNode - The camera node. + * @param {Camera} camera - The camera. + * @param {number} renderMode - The render mode flags. + * @returns {GSplatManager} The created manager. + * @private */ - constructor(device, director, layer, cameraNode) { - this.gsplatManager = new GSplatManager(device, director, layer, cameraNode); + createManager(device, director, layer, cameraNode, camera, renderMode) { + const manager = new GSplatManager(device, director, layer, cameraNode); + manager.setRenderMode(renderMode); + + // Fire material:created event + if (director.eventHandler) { + director.eventHandler.fire('material:created', manager.material, camera, layer); + } + + return manager; + } + + /** + * Updates the manager configuration based on current layer placements. + * + * @param {GraphicsDevice} device - The graphics device. + * @param {GSplatDirector} director - The director. + * @param {Layer} layer - The layer. + * @param {Camera} camera - The camera. + */ + updateConfiguration(device, director, layer, camera) { + const cameraNode = camera.node; + const hasNormalPlacements = layer.gsplatPlacements.length > 0; + const hasShadowCasters = layer.gsplatShadowCasters.length > 0; + + // Determine desired configuration + const setsEqual = SetUtils.equals(layer.gsplatPlacementsSet, layer.gsplatShadowCastersSet); + const useSharedManager = setsEqual && hasNormalPlacements; + + // Desired render modes for each manager (0 = should not exist) + const desiredMainMode = useSharedManager ? + (GSPLAT_FORWARD | GSPLAT_SHADOW) : + (hasNormalPlacements ? GSPLAT_FORWARD : 0); + const desiredShadowMode = useSharedManager ? + 0 : + (hasShadowCasters ? GSPLAT_SHADOW : 0); + + // Update or create/destroy main manager + if (desiredMainMode) { + if (this.gsplatManager) { + this.gsplatManager.setRenderMode(desiredMainMode); + } else { + this.gsplatManager = this.createManager(device, director, layer, cameraNode, camera, desiredMainMode); + } + } else if (this.gsplatManager) { + this.gsplatManager.destroy(); + this.gsplatManager = null; + } + + // Update or create/destroy shadow manager + if (desiredShadowMode) { + if (this.gsplatManagerShadow) { + this.gsplatManagerShadow.setRenderMode(desiredShadowMode); + } else { + this.gsplatManagerShadow = this.createManager(device, director, layer, cameraNode, camera, desiredShadowMode); + } + } else if (this.gsplatManagerShadow) { + this.gsplatManagerShadow.destroy(); + this.gsplatManagerShadow = null; + } } destroy() { - this.gsplatManager.destroy(); + this.gsplatManager?.destroy(); + this.gsplatManager = null; + + this.gsplatManagerShadow?.destroy(); + this.gsplatManagerShadow = null; } } @@ -62,17 +148,11 @@ class GSplatCameraData { } } - getLayerData(device, director, layer, cameraNode) { + getLayerData(device, director, layer, camera) { let layerData = this.layersMap.get(layer); if (!layerData) { - layerData = new GSplatLayerData(device, director, layer, cameraNode); + layerData = new GSplatLayerData(device, director, layer, camera); this.layersMap.set(layer, layerData); - - // Fire event that material was created - const material = layerData.gsplatManager.material; - if (material && director.eventHandler) { - director.eventHandler.fire('material:created', material, cameraNode.camera, layer); - } } return layerData; } @@ -96,11 +176,6 @@ class GSplatDirector { */ camerasMap = new Map(); - /** - * @type {GSplatAssetLoaderBase} - */ - assetLoader; - /** * @type {Scene} */ @@ -115,13 +190,11 @@ class GSplatDirector { * @param {GraphicsDevice} device - The graphics device. * @param {Renderer} renderer - The renderer. * @param {Scene} scene - The scene. - * @param {GSplatAssetLoaderBase} assetLoader - The asset loader. * @param {EventHandler} eventHandler - Event handler for firing events. */ - constructor(device, renderer, scene, assetLoader, eventHandler) { + constructor(device, renderer, scene, eventHandler) { this.device = device; this.renderer = renderer; - this.assetLoader = assetLoader; this.scene = scene; this.eventHandler = eventHandler; } @@ -160,12 +233,25 @@ class GSplatDirector { } else { // camera still exists // remove all layerdata for removed / disabled layers of this camera + // Collect layers to remove (don't modify map during iteration) cameraData.layersMap.forEach((layerData, layer) => { if (!camera.layersSet.has(layer.id) || !layer.enabled) { + tempLayersToRemove.push(layer); + } + }); + + // Now safely remove them + for (let i = 0; i < tempLayersToRemove.length; i++) { + const layer = tempLayersToRemove[i]; + const layerData = cameraData.layersMap.get(layer); + if (layerData) { layerData.destroy(); cameraData.layersMap.delete(layer); } - }); + } + + // Clear to avoid dangling references + tempLayersToRemove.length = 0; } }); @@ -187,21 +273,30 @@ class GSplatDirector { // if layer's splat placements were modified, or new camera if (layer.gsplatPlacementsDirty || !cameraData) { - // no splats on layer - const placements = layer.gsplatPlacements; - if (placements.length === 0) { + // check if there are any placements + const hasNormalPlacements = layer.gsplatPlacements.length > 0; + const hasShadowCasters = layer.gsplatShadowCasters.length > 0; - // remove gsplat manager if it exists + if (!hasNormalPlacements && !hasShadowCasters) { + // no splats on layer - remove gsplat managers if they exist if (cameraData) { cameraData.removeLayerData(layer); } - } else { - - // update gsplat manager with modified placements + // update gsplat managers with modified placements cameraData ??= this.getCameraData(camera); - const layerData = cameraData.getLayerData(this.device, this, layer, camera.node); - layerData.gsplatManager.reconcile(placements); + const layerData = cameraData.getLayerData(this.device, this, layer, camera); + + // Update configuration (creates/destroys/reconfigures managers as needed) + layerData.updateConfiguration(this.device, this, layer, camera); + + // Reconcile the managers with their respective placements + if (layerData.gsplatManager) { + layerData.gsplatManager.reconcile(layer.gsplatPlacements); + } + if (layerData.gsplatManagerShadow) { + layerData.gsplatManagerShadow.reconcile(layer.gsplatShadowCasters); + } } } } @@ -210,7 +305,12 @@ class GSplatDirector { // update gsplat managers if (cameraData) { for (const layerData of cameraData.layersMap.values()) { - gsplatCount += layerData.gsplatManager.update(); + if (layerData.gsplatManager) { + gsplatCount += layerData.gsplatManager.update(); + } + if (layerData.gsplatManagerShadow) { + gsplatCount += layerData.gsplatManagerShadow.update(); + } } } } @@ -218,8 +318,8 @@ class GSplatDirector { // update stats this.renderer._gsplatCount = gsplatCount; - // clear global gsplat params dirty flag after all updates for this camera - this.scene.gsplat.dirty = false; + // clear dirty flags + this.scene.gsplat.frameEnd(); // clear dirty flags on all layers of the composition for (let i = 0; i < comp.layerList.length; i++) { diff --git a/src/scene/gsplat-unified/gsplat-manager.js b/src/scene/gsplat-unified/gsplat-manager.js index dc52e76cb72..0626c812e49 100644 --- a/src/scene/gsplat-unified/gsplat-manager.js +++ b/src/scene/gsplat-unified/gsplat-manager.js @@ -17,6 +17,8 @@ import { Color } from '../../core/math/color.js'; * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { GSplatPlacement } from './gsplat-placement.js' * @import { Scene } from '../scene.js' + * @import { Layer } from '../layer.js' + * @import { GSplatDirector } from './gsplat-director.js' */ const cameraPosition = new Vec3(); @@ -35,7 +37,10 @@ const _lodColorsRaw = [ [0, 1, 0], // green [0, 0, 1], // blue [1, 1, 0], // yellow - [1, 0, 1] // magenta + [1, 0, 1], // magenta + [0, 1, 1], // cyan + [1, 0.5, 0], // orange + [0.5, 0, 1] // purple ]; // Color instances used by debug wireframe rendering @@ -44,7 +49,10 @@ const _lodColors = [ new Color(0, 1, 0), new Color(0, 0, 1), new Color(1, 1, 0), - new Color(1, 0, 1) + new Color(1, 0, 1), + new Color(0, 1, 1), + new Color(1, 0.5, 0), + new Color(0.5, 0, 1) ]; let _randomColorRaw = null; @@ -62,9 +70,6 @@ class GSplatManager { /** @type {GraphNode} */ node = new GraphNode('GSplatManager'); - /** @type {number} */ - cooldownTicks; - /** @type {GSplatWorkBuffer} */ workBuffer; @@ -149,6 +154,19 @@ class GSplatManager { */ hasNewOctreeInstances = false; + /** + * Bitmask flags controlling which render passes this manager participates in. + * + * @type {number|undefined} + */ + renderMode; + + /** + * @param {GraphicsDevice} device - The graphics device. + * @param {GSplatDirector} director - The director. + * @param {Layer} layer - The layer. + * @param {GraphNode} cameraNode - The camera node. + */ constructor(device, director, layer, cameraNode) { this.device = device; this.scene = director.scene; @@ -157,10 +175,43 @@ class GSplatManager { this.workBuffer = new GSplatWorkBuffer(device); this.renderer = new GSplatRenderer(device, this.node, this.cameraNode, layer, this.workBuffer); this.sorter = this.createSorter(); - this.cooldownTicks = this.director.assetLoader.cooldownTicks; + } + + /** + * Sets the render mode for this manager and its renderer. + * + * @param {number} renderMode - Bitmask flags controlling render passes (GSPLAT_FORWARD, GSPLAT_SHADOW, or both). + * @ignore + */ + setRenderMode(renderMode) { + this.renderMode = renderMode; + this.renderer.setRenderMode(renderMode); } destroy() { + this._destroyed = true; + + // Clean up all world states and decrement refs + for (const [, worldState] of this.worldStates) { + for (const splat of worldState.splats) { + splat.resource.decRefCount(); + } + worldState.destroy(); + } + this.worldStates.clear(); + + // Destroy all octree instances (they handle their own ref count cleanup) + for (const [, instance] of this.octreeInstances) { + instance.destroy(); + } + this.octreeInstances.clear(); + + // Also destroy any queued instances + for (const instance of this.octreeInstancesToDestroy) { + instance.destroy(); + } + this.octreeInstancesToDestroy.length = 0; + this.workBuffer.destroy(); this.renderer.destroy(); this.sorter.destroy(); @@ -172,7 +223,7 @@ class GSplatManager { createSorter() { // create sorter - const sorter = new GSplatUnifiedSorter(); + const sorter = new GSplatUnifiedSorter(this.scene); sorter.on('sorted', (count, version, orderData) => { this.onSorted(count, version, orderData); }); @@ -193,7 +244,8 @@ class GSplatManager { // make sure octree instance exists for placement if (!this.octreeInstances.has(p)) { - this.octreeInstances.set(p, new GSplatOctreeInstance(p.resource.octree, p, this.director.assetLoader)); + // @ts-ignore - p.resource is GSplatOctreeResource so octree cannot be null + this.octreeInstances.set(p, new GSplatOctreeInstance(this.device, p.resource.octree, p)); // mark that we have new instances that need initial LOD evaluation this.hasNewOctreeInstances = true; @@ -275,6 +327,11 @@ class GSplatManager { const newState = new GSplatWorldState(this.device, this.lastWorldStateVersion, splats); + // increment ref count for all resources in new state + for (const splat of newState.splats) { + splat.resource.incRefCount(); + } + // collect file-release requests from octree instances. for (const [, inst] of this.octreeInstances) { if (inst.removedCandidates && inst.removedCandidates.size) { @@ -316,6 +373,10 @@ class GSplatManager { for (let v = this.sortedVersion; v < version; v++) { const oldState = this.worldStates.get(v); if (oldState) { + // decrement ref count for all resources in old state + for (const splat of oldState.splats) { + splat.resource.decRefCount(); + } this.worldStates.delete(v); oldState.destroy(); } @@ -357,9 +418,10 @@ class GSplatManager { // apply pending file-release requests if (worldState.pendingReleases && worldState.pendingReleases.length) { + const cooldownTicks = this.scene.gsplat.cooldownTicks; for (const [octree, fileIndex] of worldState.pendingReleases) { // decrement once for each staged release; refcount system guards against premature unload - octree.decRefCount(fileIndex, this.cooldownTicks); + octree.decRefCount(fileIndex, cooldownTicks); } worldState.pendingReleases.length = 0; } @@ -372,7 +434,7 @@ class GSplatManager { this.workBuffer.setOrderData(orderData); // update renderer with new order data - this.renderer.frameUpdate(this.scene.gsplat); + this.renderer.setOrderData(); } } @@ -500,6 +562,21 @@ class GSplatManager { return _cameraDeltas; } + /** + * Fires the frame:ready event with current sorting and loading state. + */ + fireFrameReadyEvent() { + const ready = this.sortedVersion === this.lastWorldStateVersion; + + // Count total pending loads from octree instances (including environment) + let loadingCount = 0; + for (const [, inst] of this.octreeInstances) { + loadingCount += inst.pendingLoadCount; + } + + this.director.eventHandler.fire('frame:ready', this.cameraNode.camera, this.renderer.layer, ready, loadingCount); + } + update() { // apply any pending sorted results @@ -535,6 +612,19 @@ class GSplatManager { anyInstanceNeedsLodUpdate ||= instNeeds; } + // Validate that resources in use haven't been unexpectedly destroyed + Debug.call(() => { + const sortedState = this.worldStates.get(this.sortedVersion); + if (sortedState) { + for (const splat of sortedState.splats) { + // Check if resource reference is null or undefined + if (!splat.resource) { + Debug.warn(`GSplatManager: Resource reference is null but still referenced in world state ${sortedState.version}`); + } + } + } + }); + // check if any octree instances have moved enough to require LOD update const threshold = this.scene.gsplat.lodUpdateDistance; for (const [, inst] of this.octreeInstances) { @@ -673,22 +763,29 @@ class GSplatManager { _splatsNeedingColorUpdate.length = 0; } + // update renderer with new order data + this.renderer.frameUpdate(this.scene.gsplat); + // Update camera tracking once at the end of the frame this.updateColorCameraTracking(); } // tick cooldowns once per frame per unique octree if (this.octreeInstances.size) { + const cooldownTicks = this.scene.gsplat.cooldownTicks; for (const [, inst] of this.octreeInstances) { const octree = inst.octree; if (!tempOctreesTicked.has(octree)) { tempOctreesTicked.add(octree); - octree.updateCooldownTick(this.director.assetLoader); + octree.updateCooldownTick(cooldownTicks); } } tempOctreesTicked.clear(); } + // fire frame:ready event + this.fireFrameReadyEvent(); + // return the number of visible splats for stats const { textureSize } = this.workBuffer; return textureSize * textureSize; @@ -743,7 +840,6 @@ class GSplatManager { }); this.sorter.setSortParams(sorterRequest, this.scene.gsplat.radialSorting); - this.renderer.updateViewport(cameraNode); } /** diff --git a/src/scene/gsplat-unified/gsplat-octree-instance.js b/src/scene/gsplat-unified/gsplat-octree-instance.js index 80f677a7cc3..9430f66e7bb 100644 --- a/src/scene/gsplat-unified/gsplat-octree-instance.js +++ b/src/scene/gsplat-unified/gsplat-octree-instance.js @@ -7,10 +7,11 @@ import { Color } from '../../core/math/color.js'; import { GSplatPlacement } from './gsplat-placement.js'; /** + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { GraphNode } from '../graph-node.js' * @import { GSplatOctree } from './gsplat-octree.js' - * @import { GSplatAssetLoaderBase } from './gsplat-asset-loader-base.js' * @import { Scene } from '../scene.js' + * @import { EventHandle } from '../../core/event-handle.js' */ const _invWorldMat = new Mat4(); @@ -30,6 +31,41 @@ const _lodColors = [ new Color(1, 0, 1) ]; +/** + * Stores LOD state for a single octree node. + * + * @ignore + */ +class NodeInfo { + /** + * Current LOD index being rendered. -1 indicates node is not visible. + * @type {number} + */ + currentLod = -1; + + /** + * Optimal LOD index based on distance/visibility (before underfill). + * @type {number} + */ + optimalLod = -1; + + /** + * Importance of this node (0..1 range, higher = more important). + * Used for budget enforcement - higher importance nodes maintain quality when budget is exceeded. + * @type {number} + */ + importance = 0; + + /** + * Resets all LOD values to -1 (invisible/uninitialized). + */ + reset() { + this.currentLod = -1; + this.optimalLod = -1; + this.importance = 0; + } +} + class GSplatOctreeInstance { /** @type {GSplatOctree} */ octree; @@ -43,15 +79,14 @@ class GSplatOctreeInstance { /** @type {boolean} */ dirtyModifiedPlacements = false; - /** @type {GSplatAssetLoaderBase} */ - assetLoader; + /** @type {GraphicsDevice} */ + device; /** - * Array of current LOD index per node. Index is nodeIndex, value is lodIndex. - * Value -1 indicates node is not visible. - * @type {Int32Array} + * Array of NodeInfo instances, one per octree node. + * @type {NodeInfo[]} */ - nodeLods; + nodeInfos; /** * Array of current placements per file. Index is fileIndex, value is GSplatPlacement or null. @@ -112,6 +147,36 @@ class GSplatOctreeInstance { */ pendingVisibleAdds = new Map(); + /** + * Cached splat budget value. + * @type {number} + */ + splatBudget = 0; + + /** + * Reusable array of node indices for budget enforcement sorting. + * Lazy-allocated on first budget enforcement, then reused. + * @type {Uint32Array|null} + * @private + */ + _nodeIndices = null; + + /** + * Returns the count of resources pending load or prefetch, including environment if loading. + * + * @type {number} + */ + get pendingLoadCount() { + let count = this.pending.size + this.prefetchPending.size; + + // Add environment if it's configured but not yet loaded + if (this.octree.environmentUrl && !this.environmentPlacement) { + count++; + } + + return count; + } + /** * Environment placement. * @type {GSplatPlacement|null} @@ -119,18 +184,28 @@ class GSplatOctreeInstance { environmentPlacement = null; /** + * Event handle for device lost event. + * + * @type {EventHandle|null} + * @private + */ + _deviceLostEvent = null; + + /** + * @param {GraphicsDevice} device - The graphics device. * @param {GSplatOctree} octree - The octree. * @param {GSplatPlacement} placement - The placement. - * @param {GSplatAssetLoaderBase} assetLoader - The asset loader. */ - constructor(octree, placement, assetLoader) { + constructor(device, octree, placement) { + this.device = device; this.octree = octree; this.placement = placement; - this.assetLoader = assetLoader; - // Initialize nodeLods array with all nodes set to -1 (not visible) - this.nodeLods = new Int32Array(octree.nodes.length); - this.nodeLods.fill(-1); + // Initialize nodeInfos array with NodeInfo instances for all nodes + this.nodeInfos = new Array(octree.nodes.length); + for (let i = 0; i < octree.nodes.length; i++) { + this.nodeInfos[i] = new NodeInfo(); + } // Initialize file placements array const numFiles = octree.files.length; @@ -139,24 +214,100 @@ class GSplatOctreeInstance { // Handle environment if configured if (octree.environmentUrl) { octree.incEnvironmentRefCount(); - octree.ensureEnvironmentResource(assetLoader); + octree.ensureEnvironmentResource(); } + + // Register device lost handler + this._deviceLostEvent = device.on('devicelost', this._onDeviceLost, this); } /** * Destroys this octree instance and clears internal references. */ destroy() { + // Only decrement refs if octree is still alive + // Skip ref counting if octree was force-destroyed (e.g., asset unloaded) + if (this.octree && !this.octree.destroyed) { + // Decrement ref counts for all files currently in use (loaded files) + const filesToDecRef = this.getFileDecrements(); + for (const fileIndex of filesToDecRef) { + this.octree.decRefCount(fileIndex, 0); + } + + // Also unload files that are pending (requested but not loaded yet) + for (const fileIndex of this.pending) { + // Skip if already in filePlacements (already handled above) + if (!this.filePlacements[fileIndex]) { + this.octree.unloadResource(fileIndex); + } + } + + // Same for prefetch pending + for (const fileIndex of this.prefetchPending) { + if (!this.filePlacements[fileIndex]) { + this.octree.unloadResource(fileIndex); + } + } + + // Clean up environment if present + if (this.environmentPlacement) { + this.octree.decEnvironmentRefCount(); + } + } + this.pending.clear(); this.pendingDecrements.clear(); this.filePlacements.length = 0; + // Clean up environment placement + if (this.environmentPlacement) { + this.activePlacements.delete(this.environmentPlacement); + this.environmentPlacement = null; + } + + // Remove device event listener + this._deviceLostEvent?.off(); + this._deviceLostEvent = null; + } + + /** + * Handles device lost event by releasing all loaded resources. + * + * @private + */ + _onDeviceLost() { + // Decrement ref counts for all currently loaded file resources + for (let i = 0; i < this.filePlacements.length; i++) { + if (this.filePlacements[i]) { + // zero cooldown, immediate unload + this.octree.decRefCount(i, 0); + } + } + + // Clear all internal state + this.filePlacements.fill(null); + this.activePlacements.clear(); + this.pending.clear(); + this.pendingDecrements.clear(); + this.removedCandidates.clear(); + this.prefetchPending.clear(); + this.pendingVisibleAdds.clear(); + + // Reset all nodes to invisible + for (const nodeInfo of this.nodeInfos) { + nodeInfo.reset(); + } + // Clean up environment if present if (this.environmentPlacement) { this.activePlacements.delete(this.environmentPlacement); - this.octree.decEnvironmentRefCount(this.assetLoader); this.environmentPlacement = null; + this.octree.unloadEnvironmentResource(); } + + // Mark that LOD needs to be re-evaluated after context restore + this.dirtyModifiedPlacements = true; + this.needsLodUpdate = true; } /** @@ -188,8 +339,11 @@ class GSplatOctreeInstance { calculateNodeLod(localCameraPosition, localCameraForward, nodeIndex, maxLod, lodDistances, lodBehindPenalty) { const node = this.octree.nodes[nodeIndex]; - // Calculate distance in local space - _dirToNode.copy(node.bounds.center).sub(localCameraPosition); + // Calculate the nearest point on the bounding box to the camera for accurate distance + node.bounds.closestPoint(localCameraPosition, _dirToNode); + + // Calculate direction from camera to nearest point on box + _dirToNode.sub(localCameraPosition); let distance = _dirToNode.length(); // Apply angular-based multiplier for nodes behind the camera when enabled @@ -269,7 +423,7 @@ class GSplatOctreeInstance { if (desiredLodIndex === optimalLodIndex) { const fi = node.lods[optimalLodIndex].fileIndex; if (fi !== -1) { - this.octree.ensureFileResource(fi, this.assetLoader); + this.octree.ensureFileResource(fi); if (!this.octree.getFileResource(fi)) { this.prefetchPending.add(fi); } @@ -283,7 +437,7 @@ class GSplatOctreeInstance { for (let lod = targetLod; lod >= optimalLodIndex; lod--) { const fi = node.lods[lod].fileIndex; if (fi !== -1) { - this.octree.ensureFileResource(fi, this.assetLoader); + this.octree.ensureFileResource(fi); if (!this.octree.getFileResource(fi)) { this.prefetchPending.add(fi); } @@ -300,6 +454,42 @@ class GSplatOctreeInstance { */ updateLod(cameraNode, params) { + const maxLod = this.octree.lodLevels - 1; + const lodDistances = this.placement.lodDistances || [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60]; + + // Clamp configured LOD range to valid bounds [0, maxLod] and ensure min <= max + const { lodRangeMin, lodRangeMax } = params; + const rangeMin = Math.max(0, Math.min(lodRangeMin ?? 0, maxLod)); + const rangeMax = Math.max(rangeMin, Math.min(lodRangeMax ?? maxLod, maxLod)); + + // Pass 1: Evaluate optimal LOD for each node (distance-based) + const totalOptimalSplats = this.evaluateNodeLods(cameraNode, maxLod, lodDistances, rangeMin, rangeMax, params); + + // Enforce splat budget if enabled (bidirectional: degrade or upgrade) + if (this.splatBudget > 0) { + this.enforceSplatBudget(totalOptimalSplats, this.splatBudget, rangeMin, rangeMax); + } + + // Pass 2: Calculate desired LOD (underfill) and apply changes + this.applyLodChanges(maxLod, params); + } + + /** + * Evaluates optimal LOD indices for all nodes based on camera position and parameters. + * This is Pass 1 of the LOD update process. Results are stored in nodeInfos array. + * + * @param {GraphNode} cameraNode - The camera node. + * @param {number} maxLod - Maximum LOD index (lodLevels - 1). + * @param {number[]} lodDistances - Array of distance thresholds per LOD. + * @param {number} rangeMin - Minimum allowed LOD index. + * @param {number} rangeMax - Maximum allowed LOD index. + * @param {import('./gsplat-params.js').GSplatParams} params - Global gsplat parameters. + * @returns {number} Total number of splats that would be used by optimal LODs. + * @private + */ + evaluateNodeLods(cameraNode, maxLod, lodDistances, rangeMin, rangeMax, params) { + const { lodBehindPenalty } = params; + // transform camera position to octree local space const worldCameraPosition = cameraNode.getPosition(); const octreeWorldTransform = this.placement.node.getWorldTransform(); @@ -308,31 +498,197 @@ class GSplatOctreeInstance { const worldCameraForward = cameraNode.forward; const localCameraForward = _invWorldMat.transformVector(worldCameraForward, _localCameraFwd).normalize(); - // calculate max LOD once for all nodes - const maxLod = this.octree.lodLevels - 1; - const lodDistances = this.placement.lodDistances || [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60]; + const nodes = this.octree.nodes; + const nodeInfos = this.nodeInfos; + let totalSplats = 0; - // parameters - const { lodBehindPenalty, lodRangeMin, lodRangeMax, lodUnderfillLimit = 0 } = params; + // Use distance threshold for max LOD range to normalize importance + const maxDistance = lodDistances[rangeMax] || 100; - // Clamp configured LOD range to valid bounds [0, maxLod] and ensure min <= max - const rangeMin = Math.max(0, Math.min(lodRangeMin ?? 0, maxLod)); - const rangeMax = Math.max(rangeMin, Math.min(lodRangeMax ?? maxLod, maxLod)); + for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { + const node = nodes[nodeIndex]; + + // Calculate the nearest point on the bounding box to the camera for accurate distance + node.bounds.closestPoint(localCameraPosition, _dirToNode); + + // Calculate direction from camera to nearest point on box + _dirToNode.sub(localCameraPosition); + const actualDistance = _dirToNode.length(); + + // Apply angular-based multiplier for nodes behind the camera when enabled + let penalizedDistance = actualDistance; + let importanceMultiplier = 1.0; + + if (lodBehindPenalty > 1 && actualDistance > 0.01) { + // dot using unnormalized direction to avoid extra normalize; divide by distance + const dotOverDistance = localCameraForward.dot(_dirToNode) / actualDistance; + + // Only apply penalty when behind the camera (dot < 0) + if (dotOverDistance < 0) { + const t = -dotOverDistance; // 0 .. 1 for front -> directly behind + const factor = 1 + t * (lodBehindPenalty - 1); + penalizedDistance = actualDistance * factor; + importanceMultiplier = 1.0 / factor; // inverse for importance + } + } + + // Find appropriate LOD based on penalized distance + let optimalLodIndex = maxLod; + for (let lod = 0; lod < maxLod; lod++) { + if (penalizedDistance < lodDistances[lod]) { + optimalLodIndex = lod; + break; + } + } + + // Clamp to configured range + if (optimalLodIndex < rangeMin) optimalLodIndex = rangeMin; + if (optimalLodIndex > rangeMax) optimalLodIndex = rangeMax; + + // Calculate importance: inverse of distance, normalized, with behind-camera penalty + const normalizedDistance = Math.min(actualDistance / maxDistance, 1.0); + const importance = (1.0 - normalizedDistance) * importanceMultiplier; + + // Store optimal LOD and importance + nodeInfos[nodeIndex].optimalLod = optimalLodIndex; + nodeInfos[nodeIndex].importance = importance; + + // Count splats for this optimal LOD + const lod = nodes[nodeIndex].lods[optimalLodIndex]; + if (lod && lod.count) { + totalSplats += lod.count; + } + } + + return totalSplats; + } + + /** + * Adjusts optimal LOD indices to fit within the splat budget bidirectionally. + * When over budget: degrades quality for lower-importance nodes first. + * When under budget: upgrades quality for higher-importance nodes first. + * Uses multiple passes, adjusting by one level per pass, until budget is reached + * or all nodes hit their respective limits (rangeMin or rangeMax). + * + * @param {number} totalSplats - Current total splat count with optimal LODs. + * @param {number} splatBudget - Target splat count to reach. + * @param {number} rangeMin - Minimum allowed LOD index. + * @param {number} rangeMax - Maximum allowed LOD index. + * @private + */ + enforceSplatBudget(totalSplats, splatBudget, rangeMin, rangeMax) { + const nodes = this.octree.nodes; + const nodeInfos = this.nodeInfos; + + // Lazy-allocate node indices array on first use + if (!this._nodeIndices) { + this._nodeIndices = new Uint32Array(nodes.length); + for (let i = 0; i < nodes.length; i++) { + this._nodeIndices[i] = i; + } + } + + // Sort node indices by importance (lowest first) - done once + const nodeIndices = this._nodeIndices; + nodeIndices.sort((a, b) => nodeInfos[a].importance - nodeInfos[b].importance); + + let currentSplats = totalSplats; + + // Skip if already at budget + if (currentSplats === splatBudget) { + return; + } + + // Determine direction and set iteration parameters + const isOverBudget = currentSplats > splatBudget; + const lodDelta = isOverBudget ? 1 : -1; + + // Multiple passes: adjust by one LOD level per pass until budget is reached + while (isOverBudget ? currentSplats > splatBudget : currentSplats < splatBudget) { + let modified = false; + + if (isOverBudget) { + + // DEGRADE: process from lowest to highest importance + for (let i = 0; i < nodeIndices.length; i++) { + const nodeIndex = nodeIndices[i]; + const nodeInfo = nodeInfos[nodeIndex]; + const node = nodes[nodeIndex]; + const currentOptimalLod = nodeInfo.optimalLod; + + // Try degrading to next coarser LOD (respect rangeMax constraint) + if (currentOptimalLod < rangeMax) { + const currentLod = node.lods[currentOptimalLod]; + const nextLod = node.lods[currentOptimalLod + 1]; + const splatsSaved = currentLod.count - nextLod.count; + + // Degrade to coarser LOD + nodeInfo.optimalLod += lodDelta; + currentSplats -= splatsSaved; + modified = true; + + if (currentSplats <= splatBudget) { + break; // Within budget + } + } + } + } else { + + // UPGRADE: process from highest to lowest importance + for (let i = nodeIndices.length - 1; i >= 0; i--) { + const nodeIndex = nodeIndices[i]; + const nodeInfo = nodeInfos[nodeIndex]; + const node = nodes[nodeIndex]; + const currentOptimalLod = nodeInfo.optimalLod; + + // Try upgrading to next finer LOD (respect rangeMin constraint) + if (currentOptimalLod > rangeMin) { + const currentLod = node.lods[currentOptimalLod]; + const nextLod = node.lods[currentOptimalLod - 1]; + const splatsAdded = nextLod.count - currentLod.count; + + // Only upgrade if we won't exceed budget + if (currentSplats + splatsAdded <= splatBudget) { + // Upgrade to finer LOD + nodeInfo.optimalLod += lodDelta; + currentSplats += splatsAdded; + modified = true; + + if (currentSplats >= splatBudget) { + break; // At budget + } + } + } + } + } + // If no nodes were modified, we can't adjust further + if (!modified) { + break; + } + } + } - // process all nodes + /** + * Applies calculated LOD changes and manages file placements. + * This is Pass 2 of the LOD update process. Reads from nodeInfos array populated by evaluateNodeLods(). + * + * @param {number} maxLod - Maximum LOD index (lodLevels - 1). + * @param {import('./gsplat-params.js').GSplatParams} params - Global gsplat parameters. + * @private + */ + applyLodChanges(maxLod, params) { const nodes = this.octree.nodes; + const { lodUnderfillLimit = 0 } = params; + for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { const node = nodes[nodeIndex]; + const nodeInfo = this.nodeInfos[nodeIndex]; - // LOD for the node, clamped by configured range - // optimal target LOD based on distance and range - let optimalLodIndex = this.calculateNodeLod(localCameraPosition, localCameraForward, nodeIndex, maxLod, lodDistances, lodBehindPenalty); - if (optimalLodIndex < rangeMin) optimalLodIndex = rangeMin; - if (optimalLodIndex > rangeMax) optimalLodIndex = rangeMax; - const currentLodIndex = this.nodeLods[nodeIndex]; + const optimalLodIndex = nodeInfo.optimalLod; + const currentLodIndex = nodeInfo.currentLod; - // Determine desired display LOD using underfill strategy within allowed range + // Apply underfill strategy to determine desired LOD for streaming const desiredLodIndex = this.selectDesiredLodIndex(node, optimalLodIndex, maxLod, lodUnderfillLimit); // if desired LOD differs from currently displayed LOD @@ -381,7 +737,7 @@ class GSplatOctreeInstance { const newPlacement = this.filePlacements[desiredFileIndex]; if (newPlacement?.resource) { // resource is ready now, display immediately - this.nodeLods[nodeIndex] = desiredLodIndex; + nodeInfo.currentLod = desiredLodIndex; // clear any pending visible-add entry this.pendingVisibleAdds.delete(nodeIndex); } else { @@ -399,7 +755,7 @@ class GSplatOctreeInstance { this.pendingDecrements.delete(nodeIndex); } this.decrementFileRef(currentFileIndex, nodeIndex); - this.nodeLods[nodeIndex] = -1; + nodeInfo.currentLod = -1; // clear any pending visible-add entry this.pendingVisibleAdds.delete(nodeIndex); @@ -415,7 +771,7 @@ class GSplatOctreeInstance { // clear any pending for this node if exists this.pendingDecrements.delete(nodeIndex); // update displayed lod now that switch is complete - this.nodeLods[nodeIndex] = desiredLodIndex; + nodeInfo.currentLod = desiredLodIndex; // clear any pending visible-add entry this.pendingVisibleAdds.delete(nodeIndex); } else { @@ -462,7 +818,7 @@ class GSplatOctreeInstance { if (!this.addFilePlacement(fileIndex)) { // resource not loaded yet, kick off load and add to pending - this.octree.ensureFileResource(fileIndex, this.assetLoader); + this.octree.ensureFileResource(fileIndex); this.pending.add(fileIndex); } } @@ -569,12 +925,19 @@ class GSplatOctreeInstance { */ update(scene) { + // Sync splat budget from placement and detect changes + const currentBudget = this.placement.splatBudget; + if (currentBudget !== this.splatBudget) { + this.splatBudget = currentBudget; + this.needsLodUpdate = true; + } + // handle pending loads if (this.pending.size) { for (const fileIndex of this.pending) { // check if the asset has finished loading and store it if so - this.octree.ensureFileResource(fileIndex, this.assetLoader); + this.octree.ensureFileResource(fileIndex); // if resource became available, update placement and execute any pending decrements if (this.addFilePlacement(fileIndex)) { @@ -595,7 +958,7 @@ class GSplatOctreeInstance { break; } } - this.nodeLods[nodeIndex] = newLodIndex; + this.nodeInfos[nodeIndex].currentLod = newLodIndex; } } } @@ -621,7 +984,7 @@ class GSplatOctreeInstance { // handle environment loading if (this.octree.environmentUrl && !this.environmentPlacement) { // poll for environment resource completion - this.octree.ensureEnvironmentResource(this.assetLoader); + this.octree.ensureEnvironmentResource(); const envResource = this.octree.environmentResource; if (envResource) { @@ -646,7 +1009,7 @@ class GSplatOctreeInstance { const modelMat = this.placement.node.getWorldTransform(); const nodes = this.octree.nodes; for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { - const lodIndex = this.nodeLods[nodeIndex]; + const lodIndex = this.nodeInfos[nodeIndex].currentLod; if (lodIndex >= 0) { const color = _lodColors[Math.min(lodIndex, _lodColors.length - 1)]; _tempDebugAabb.setFromTransformedAabb(nodes[nodeIndex].bounds, modelMat); @@ -676,7 +1039,7 @@ class GSplatOctreeInstance { // poll loader and store resource in octree if ready for (const fileIndex of this.prefetchPending) { - this.octree.ensureFileResource(fileIndex, this.assetLoader); + this.octree.ensureFileResource(fileIndex); if (this.octree.getFileResource(fileIndex)) { _tempCompletedUrls.push(fileIndex); } diff --git a/src/scene/gsplat-unified/gsplat-octree.js b/src/scene/gsplat-unified/gsplat-octree.js index 3aa5f65ba8c..9db41ae8829 100644 --- a/src/scene/gsplat-unified/gsplat-octree.js +++ b/src/scene/gsplat-unified/gsplat-octree.js @@ -80,6 +80,28 @@ class GSplatOctree { */ environmentRefCount = 0; + /** + * Asset loader used for loading/unloading resources. + * + * @type {GSplatAssetLoaderBase|null} + */ + assetLoader = null; + + /** + * Whether this octree has been destroyed. + * + * @type {boolean} + */ + destroyed = false; + + /** + * Number of update ticks before unloading unused file resources. Set from GSplatParams. + * + * @type {number} + * @private + */ + cooldownTicks = 100; + /** * @param {string} assetFileUrl - The file URL of the container asset. * @param {Object} data - The parsed JSON data containing info, filenames and tree. @@ -143,6 +165,25 @@ class GSplatOctree { }); } + /** + * Destroys the octree and clears internal state. Does not force-unload resources as they may + * still be referenced by managers. Resources will be cleaned up when their reference counts + * reach zero through the normal cleanup mechanisms. + */ + destroy() { + // Mark as destroyed so instances can detect forced cleanup + this.destroyed = true; + + // Clear internal state + this.fileResources.clear(); + this.cooldowns.clear(); + + // Destroy and clear references + this.assetLoader?.destroy(); + this.assetLoader = null; + this.environmentResource = null; + } + /** * Trace out per-LOD counts of currently loaded file resources. * @private @@ -206,10 +247,12 @@ class GSplatOctree { } /** - * Decrements reference count for a file by index. When it reaches zero, start cooldown. + * Decrements reference count for a file by index. When it reaches zero, either unload + * immediately (if cooldownTicks is 0) or schedule for cooldown. * * @param {number} fileIndex - Index of the file in `files` array. - * @param {number} cooldownTicks - Number of update ticks before unloading when unused. + * @param {number} cooldownTicks - Number of update ticks before unloading when unused. If 0, + * unload immediately. */ decRefCount(fileIndex, cooldownTicks) { Debug.assert(fileIndex >= 0 && fileIndex < this.files.length); @@ -218,9 +261,15 @@ class GSplatOctree { this.fileRefCounts[fileIndex] = count; Debug.assert(count >= 0); - // unload cooldown + // When ref count reaches zero if (count === 0) { - this.cooldowns.set(fileIndex, cooldownTicks); + if (cooldownTicks === 0) { + // Unload immediately (e.g., during device loss) + this.unloadResource(fileIndex); + } else { + // Schedule for cooldown + this.cooldowns.set(fileIndex, cooldownTicks); + } } } @@ -228,15 +277,22 @@ class GSplatOctree { * Unloads a resource for a file index if currently loaded. * * @param {number} fileIndex - Index of the file in `files` array. - * @param {GSplatAssetLoaderBase} assetLoader - Asset loader used to unload the resource. */ - unloadResource(fileIndex, assetLoader) { + unloadResource(fileIndex) { Debug.assert(fileIndex >= 0 && fileIndex < this.files.length); - if (this.fileResources.has(fileIndex)) { + // If octree was destroyed, assetLoader is null - nothing to unload + if (!this.assetLoader) { + return; + } - const fullUrl = this.files[fileIndex].url; - assetLoader.unload(fullUrl); + const fullUrl = this.files[fileIndex].url; + + // Always call unload - it handles loaded, loading, and queued resources + this.assetLoader.unload(fullUrl); + + // Clean up loaded resource if present + if (this.fileResources.has(fileIndex)) { this.fileResources.delete(fileIndex); // trace updated LOD counts after change @@ -247,16 +303,18 @@ class GSplatOctree { /** * Advances cooldowns for zero-ref files and unloads those whose timers expired. * - * @param {GSplatAssetLoaderBase} assetLoader - Asset loader used to unload expired resources. + * @param {number} cooldownTicks - Number of ticks for new cooldowns, synced from GSplatParams. */ - updateCooldownTick(assetLoader) { + updateCooldownTick(cooldownTicks) { + this.cooldownTicks = cooldownTicks; + if (this.cooldowns.size > 0) { this.cooldowns.forEach((remaining, fileIndex) => { if (remaining <= 1) { // just a safety to avoid unloading a file that was re-referenced if (this.fileRefCounts[fileIndex] === 0) { - this.unloadResource(fileIndex, assetLoader); + this.unloadResource(fileIndex); } _toDelete.push(fileIndex); } else { @@ -278,10 +336,10 @@ class GSplatOctree { * - Checks if loading completed and stores the resource if available * * @param {number} fileIndex - The index of the file in the `files` array. - * @param {GSplatAssetLoaderBase} assetLoader - The asset loader. */ - ensureFileResource(fileIndex, assetLoader) { + ensureFileResource(fileIndex) { Debug.assert(fileIndex >= 0 && fileIndex < this.files.length); + Debug.assert(this.assetLoader); // resource already loaded if (this.fileResources.has(fileIndex)) { @@ -290,13 +348,13 @@ class GSplatOctree { // Check if the resource is now available from the asset loader const fullUrl = this.files[fileIndex].url; - const res = assetLoader.getResource(fullUrl); + const res = this.assetLoader?.getResource(fullUrl); if (res) { this.fileResources.set(fileIndex, res); // if the file finished loading and is no longer needed, schedule a cooldown if (this.fileRefCounts[fileIndex] === 0) { - this.cooldowns.set(fileIndex, assetLoader.cooldownTicks); + this.cooldowns.set(fileIndex, this.cooldownTicks); } // trace updated LOD counts after change @@ -306,7 +364,7 @@ class GSplatOctree { } // Start/continue loading (asset loader handles duplicates internally) - assetLoader.load(fullUrl); + this.assetLoader?.load(fullUrl); } /** @@ -318,25 +376,26 @@ class GSplatOctree { /** * Decrements reference count for environment. When it reaches zero, immediately unload. - * - * @param {GSplatAssetLoaderBase} assetLoader - Asset loader used to unload the resource. */ - decEnvironmentRefCount(assetLoader) { + decEnvironmentRefCount() { this.environmentRefCount--; Debug.assert(this.environmentRefCount >= 0); // unload immediately when reaching zero if (this.environmentRefCount === 0) { - this.unloadEnvironmentResource(assetLoader); + this.unloadEnvironmentResource(); } } /** * Ensures environment resource is loaded and available. - * - * @param {GSplatAssetLoaderBase} assetLoader - The asset loader. */ - ensureEnvironmentResource(assetLoader) { + ensureEnvironmentResource() { + // If octree was destroyed, don't load anything + if (!this.assetLoader) { + return; + } + // no environment configured if (!this.environmentUrl) { return; @@ -348,30 +407,33 @@ class GSplatOctree { } // Check if the resource is now available from the asset loader - const res = assetLoader.getResource(this.environmentUrl); + const res = this.assetLoader.getResource(this.environmentUrl); if (res) { this.environmentResource = res; // if loaded but not needed, immediately unload if (this.environmentRefCount === 0) { - this.unloadEnvironmentResource(assetLoader); + this.unloadEnvironmentResource(); } return; } // Start/continue loading (asset loader handles duplicates internally) - assetLoader.load(this.environmentUrl); + this.assetLoader.load(this.environmentUrl); } /** * Unloads environment resource if currently loaded. - * - * @param {GSplatAssetLoaderBase} assetLoader - Asset loader used to unload the resource. */ - unloadEnvironmentResource(assetLoader) { + unloadEnvironmentResource() { + // If octree was destroyed, assetLoader is null - nothing to unload + if (!this.assetLoader) { + return; + } + if (this.environmentResource && this.environmentUrl) { - assetLoader.unload(this.environmentUrl); + this.assetLoader.unload(this.environmentUrl); this.environmentResource = null; } } diff --git a/src/scene/gsplat-unified/gsplat-octree.resource.js b/src/scene/gsplat-unified/gsplat-octree.resource.js index bddcdfdabd0..cdbbc9f0a86 100644 --- a/src/scene/gsplat-unified/gsplat-octree.resource.js +++ b/src/scene/gsplat-unified/gsplat-octree.resource.js @@ -6,17 +6,27 @@ class GSplatOctreeResource { /** @type {BoundingBox} */ aabb = new BoundingBox(); - /** @type {GSplatOctree} */ + /** @type {GSplatOctree|null} */ octree; /** * @param {string} assetFileUrl - The file URL of the container asset. * @param {object} data - Parsed JSON data. + * @param {object} assetLoader - Asset loader instance (framework-level object). */ - constructor(assetFileUrl, data) { + constructor(assetFileUrl, data, assetLoader) { this.octree = new GSplatOctree(assetFileUrl, data); + this.octree.assetLoader = assetLoader; this.aabb.setMinMax(new Vec3(data.tree.bound.min), new Vec3(data.tree.bound.max)); } + + /** + * Destroys the octree resource and cleans up all associated resources. + */ + destroy() { + this.octree?.destroy(); + this.octree = null; + } } export { GSplatOctreeResource }; diff --git a/src/scene/gsplat-unified/gsplat-params.js b/src/scene/gsplat-unified/gsplat-params.js index f14507127d7..3d59323318f 100644 --- a/src/scene/gsplat-unified/gsplat-params.js +++ b/src/scene/gsplat-unified/gsplat-params.js @@ -1,9 +1,22 @@ +import { Debug } from '../../core/debug.js'; +import { ShaderMaterial } from '../materials/shader-material.js'; + +/** + * @import { Texture } from '../../platform/graphics/texture.js' + */ + /** * Parameters for GSplat unified system. * * @category Graphics */ class GSplatParams { + /** + * @type {ShaderMaterial} + * @private + */ + _material = new ShaderMaterial(); + /** * Enables debug rendering of AABBs for GSplat objects. Defaults to false. * @@ -197,6 +210,15 @@ class GSplatParams { return this._lodUnderfillLimit; } + set splatBudget(value) { + Debug.removed('GSplatParams.splatBudget is deprecated. Use GSplatComponent.splatBudget instead to set per-component budgets.'); + } + + get splatBudget() { + Debug.removed('GSplatParams.splatBudget is deprecated. Use GSplatComponent.splatBudget instead to set per-component budgets.'); + return 0; + } + /** * @type {import('../../platform/graphics/texture.js').Texture|null} * @private @@ -209,7 +231,7 @@ class GSplatParams { * Texture should be (width x 1) size. World Y coordinate (0-20 range) maps to texture U coordinate. * Defaults to null. * - * @type {import('../../platform/graphics/texture.js').Texture|null} + * @type {Texture|null} */ set colorRamp(value) { if (this._colorRamp !== value) { @@ -284,6 +306,42 @@ class GSplatParams { * @type {number} */ colorUpdateAngleLodScale = 2; + + /** + * Number of update ticks before unloading unused streamed resources. When a streamed resource's + * reference count reaches zero, it enters a cooldown period before being unloaded. This allows + * recently used data to remain in memory for quick reuse if needed again soon. Set to 0 to + * unload immediately when unused. Defaults to 100. + * + * @type {number} + */ + cooldownTicks = 100; + + /** + * A material template that can be customized by the user. Any defines, parameters, or shader + * chunks set on this material will be automatically applied to all GSplat components rendered + * in unified mode. After making changes, call {@link Material#update} to for the changes to be applied + * on the next frame. + * + * @type {ShaderMaterial} + * @example + * // Set a custom parameter on all GSplat materials + * app.scene.gsplat.material.setParameter('alphaClip', 0.4); + * app.scene.gsplat.material.update(); + */ + get material() { + return this._material; + } + + /** + * Called at the end of the frame to clear dirty flags. + * + * @ignore + */ + frameEnd() { + this._material.dirty = false; + this.dirty = false; + } } export { GSplatParams }; diff --git a/src/scene/gsplat-unified/gsplat-placement.js b/src/scene/gsplat-unified/gsplat-placement.js index 304db46eeda..471253de6e4 100644 --- a/src/scene/gsplat-unified/gsplat-placement.js +++ b/src/scene/gsplat-unified/gsplat-placement.js @@ -51,6 +51,13 @@ class GSplatPlacement { */ _lodDistances = null; + /** + * Target number of splats to render for this placement. Set to 0 to disable (default). + * + * @type {number} + */ + splatBudget = 0; + /** * The axis-aligned bounding box for this placement, in local space. * diff --git a/src/scene/gsplat-unified/gsplat-renderer.js b/src/scene/gsplat-unified/gsplat-renderer.js index 22a9eac6e86..85bf7094308 100644 --- a/src/scene/gsplat-unified/gsplat-renderer.js +++ b/src/scene/gsplat-unified/gsplat-renderer.js @@ -1,5 +1,8 @@ import { SEMANTIC_POSITION, SEMANTIC_ATTR13, CULLFACE_NONE, PIXELFORMAT_RGBA16U } from '../../platform/graphics/constants.js'; -import { BLEND_NONE, BLEND_PREMULTIPLIED, BLEND_ADDITIVE } from '../constants.js'; +import { + BLEND_NONE, BLEND_PREMULTIPLIED, BLEND_ADDITIVE, GSPLAT_FORWARD, GSPLAT_SHADOW, + SHADOWCAMERA_NAME +} from '../constants.js'; import { ShaderMaterial } from '../materials/shader-material.js'; import { GSplatResourceBase } from '../gsplat/gsplat-resource-base.js'; import { MeshInstance } from '../mesh-instance.js'; @@ -9,6 +12,8 @@ import { math } from '../../core/math/math.js'; * @import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js' * @import { Layer } from '../layer.js' * @import { GraphNode } from '../graph-node.js' + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + * @import { GSplatWorkBuffer } from './gsplat-work-buffer.js' */ /** @@ -35,11 +40,25 @@ class GSplatRenderer { /** @type {GraphNode} */ cameraNode; - viewportParams = [0, 0]; - /** @type {number} */ originalBlendType = BLEND_ADDITIVE; + /** @type {number|undefined} */ + renderMode; + + /** @type {Set} */ + _internalDefines = new Set(); + + /** @type {boolean} */ + forceCopyMaterial = true; + + /** + * @param {GraphicsDevice} device - The graphics device. + * @param {GraphNode} node - The graph node. + * @param {GraphNode} cameraNode - The camera node. + * @param {Layer} layer - The layer to add mesh instances to. + * @param {GSplatWorkBuffer} workBuffer - The work buffer containing splat data. + */ constructor(device, node, cameraNode, layer, workBuffer) { this.device = device; this.node = node; @@ -62,12 +81,62 @@ class GSplatRenderer { this.configureMaterial(); + // Capture internal define names to protect them from being cleared + this._material.defines.forEach((value, key) => { + this._internalDefines.add(key); + }); + this.meshInstance = this.createMeshInstance(); - layer.addMeshInstances([this.meshInstance]); + } + + /** + * Sets the render mode for this renderer, managing layer array membership. + * + * @param {number} renderMode - Bitmask flags controlling render passes (GSPLAT_FORWARD, GSPLAT_SHADOW, or both). + */ + setRenderMode(renderMode) { + const oldRenderMode = this.renderMode ?? 0; + + // Calculate what changed + const wasForward = (oldRenderMode & GSPLAT_FORWARD) !== 0; + const wasShadow = (oldRenderMode & GSPLAT_SHADOW) !== 0; + const isForward = (renderMode & GSPLAT_FORWARD) !== 0; + const isShadow = (renderMode & GSPLAT_SHADOW) !== 0; + + // Update mesh instance castShadow state FIRST, before adding to arrays + this.meshInstance.castShadow = isShadow; + + // Remove from old arrays if needed + if (wasForward && !isForward) { + this.layer.removeMeshInstances([this.meshInstance], true); + } + if (wasShadow && !isShadow) { + this.layer.removeShadowCasters([this.meshInstance]); + } + + // Add to new arrays if needed + if (!wasForward && isForward) { + this.layer.addMeshInstances([this.meshInstance], true); + } + if (!wasShadow && isShadow) { + this.layer.addShadowCasters([this.meshInstance]); + } + + // Update state + this.renderMode = renderMode; } destroy() { - this.layer.removeMeshInstances([this.meshInstance]); + // Remove mesh instance from appropriate layer arrays based on render mode + if (this.renderMode) { + if (this.renderMode & GSPLAT_FORWARD) { + this.layer.removeMeshInstances([this.meshInstance], true); + } + if (this.renderMode & GSPLAT_SHADOW) { + this.layer.removeShadowCasters([this.meshInstance]); + } + } + this._material.destroy(); this.meshInstance.destroy(); } @@ -123,19 +192,65 @@ class GSplatRenderer { this.meshInstance.visible = count > 0; } - frameUpdate(params) { - + setOrderData() { // Set the appropriate order data resource based on device type if (this.device.isWebGPU) { this._material.setParameter('splatOrder', this.workBuffer.orderBuffer); } else { this._material.setParameter('splatOrder', this.workBuffer.orderTexture); } + } + + frameUpdate(params) { // Update colorRampIntensity parameter every frame when overdraw is enabled if (params.colorRamp) { this._material.setParameter('colorRampIntensity', params.colorRampIntensity); } + + // Copy material settings from params.material if dirty or on first update + if (this.forceCopyMaterial || params.material.dirty) { + this.copyMaterialSettings(params.material); + this.forceCopyMaterial = false; + } + } + + /** + * Copies material settings from a source material to the internal material. + * Preserves internal defines while copying user defines, parameters, and shader chunks. + * + * @param {ShaderMaterial} sourceMaterial - The source material to copy settings from. + * @private + */ + copyMaterialSettings(sourceMaterial) { + // Clear user defines (preserve internal defines) + const keysToDelete = []; + this._material.defines.forEach((value, key) => { + if (!this._internalDefines.has(key)) { + keysToDelete.push(key); + } + }); + keysToDelete.forEach(key => this._material.defines.delete(key)); + + // Copy defines from source material + sourceMaterial.defines.forEach((value, key) => { + this._material.defines.set(key, value); + }); + + // Copy parameters + const srcParams = sourceMaterial.parameters; + for (const paramName in srcParams) { + if (srcParams.hasOwnProperty(paramName)) { + this._material.setParameter(paramName, srcParams[paramName].data); + } + } + + // Copy shader chunks if they exist + if (sourceMaterial.hasShaderChunks) { + this._material.shaderChunks.copy(sourceMaterial.shaderChunks); + } + + this._material.update(); } updateOverdrawMode(params) { @@ -201,30 +316,22 @@ class GSplatRenderer { // TODO: consider using aabb as well to avoid rendering off-screen splats const thisCamera = this.cameraNode.camera; meshInstance.isVisibleFunc = (camera) => { - const vis = thisCamera.camera === camera; - return vis; - }; - - return meshInstance; - } + const renderMode = this.renderMode ?? 0; - updateViewport(cameraNode) { - const camera = cameraNode.camera; - const cameraRect = camera.rect; - const renderTarget = camera?.renderTarget; - const { width, height } = renderTarget ?? this.device; + // visible for main camera in forward rendering mode + if (thisCamera.camera === camera && (renderMode & GSPLAT_FORWARD)) { + return true; + } - const viewport = this.viewportParams; - viewport[0] = width * cameraRect.z; - viewport[1] = height * cameraRect.w; + // visible for shadow cameras in shadow rendering mode + if (renderMode & GSPLAT_SHADOW) { + return camera.node?.name === SHADOWCAMERA_NAME; + } - // adjust viewport for stereoscopic VR sessions - const xr = camera?.camera?.xr; - if (xr?.active && xr.views.list.length === 2) { - viewport[0] *= 0.5; - } + return false; + }; - this._material.setParameter('viewport', viewport); + return meshInstance; } } diff --git a/src/scene/gsplat-unified/gsplat-unified-sort-worker.js b/src/scene/gsplat-unified/gsplat-unified-sort-worker.js index a8078d5bee6..f383b5ad53b 100644 --- a/src/scene/gsplat-unified/gsplat-unified-sort-worker.js +++ b/src/scene/gsplat-unified/gsplat-unified-sort-worker.js @@ -279,6 +279,7 @@ function UnifiedSortWorker() { }; const sort = (sortParams, order, centersData) => { + const sortStartTime = performance.now(); // distance bounds from AABB projections per splat const { minDist, maxDist } = _radialSort ? @@ -330,11 +331,13 @@ function UnifiedSortWorker() { const count = numVertices; // send results + const sortTime = performance.now() - sortStartTime; const transferList = [order.buffer]; const response = { order: order.buffer, count, - version: centersData.version + version: centersData.version, + sortTime: sortTime }; myself.postMessage(response, transferList); diff --git a/src/scene/gsplat-unified/gsplat-unified-sorter.js b/src/scene/gsplat-unified/gsplat-unified-sorter.js index 0936d9b7952..039d0ed650a 100644 --- a/src/scene/gsplat-unified/gsplat-unified-sorter.js +++ b/src/scene/gsplat-unified/gsplat-unified-sorter.js @@ -4,6 +4,7 @@ import { UnifiedSortWorker } from './gsplat-unified-sort-worker.js'; /** * @import { GSplatInfo } from './gsplat-info.js' + * @import { Scene } from '../scene.js' */ /** @type {Set} */ @@ -36,8 +37,15 @@ class GSplatUnifiedSorter extends EventHandler { /** @type {boolean} */ _destroyed = false; - constructor() { + /** @type {Scene|null} */ + scene = null; + + /** + * @param {Scene} [scene] - The scene to fire sort timing events on. + */ + constructor(scene) { super(); + this.scene = scene ?? null; const workerSource = `(${UnifiedSortWorker.toString()})()`; @@ -61,6 +69,12 @@ class GSplatUnifiedSorter extends EventHandler { } const msgData = message.data ?? message; + + // Fire sortTime event directly on scene (before result might be dropped) + if (this.scene && msgData.sortTime !== undefined) { + this.scene.fire('gsplat:sorted', msgData.sortTime); + } + const orderData = new Uint32Array(msgData.order); // decrement jobs in flight counter diff --git a/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js b/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js index a5ec057cecd..40ec5230a77 100644 --- a/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js +++ b/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js @@ -1,5 +1,7 @@ import { Debug } from '../../core/debug.js'; import { Mat4 } from '../../core/math/mat4.js'; +import { Vec3 } from '../../core/math/vec3.js'; +import { Quat } from '../../core/math/quat.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; @@ -14,6 +16,8 @@ import { CULLFACE_NONE } from '../../platform/graphics/constants.js'; */ const _viewMat = new Mat4(); +const _modelScale = new Vec3(); +const _modelRotation = new Quat(); const _whiteColor = [1, 1, 1]; @@ -46,6 +50,12 @@ class GSplatWorkBufferRenderPass extends RenderPass { /** @type {boolean} */ colorOnly; + /** @type {Float32Array} */ + _modelScaleData = new Float32Array(3); + + /** @type {Float32Array} */ + _modelRotationData = new Float32Array(4); + constructor(device, workBuffer, colorOnly = false) { super(device); this.workBuffer = workBuffer; @@ -133,9 +143,6 @@ class GSplatWorkBufferRenderPass extends RenderPass { // Assign material properties to scope workBufferRenderInfo.material.setParameters(device); - // Matrix to transform splats to the world space - scope.resolve('uTransform').setValue(splatInfo.node.getWorldTransform().data); - if (intervalTexture) { // Set LOD intervals texture for remapping of indices scope.resolve('uIntervalsTexture').setValue(intervalTexture.texture); @@ -149,8 +156,28 @@ class GSplatWorkBufferRenderPass extends RenderPass { const color = this.colorsByLod?.[splatInfo.lodIndex] ?? this.colorsByLod?.[0] ?? _whiteColor; scope.resolve('uColorMultiply').setValue(color); - // SH related - scope.resolve('matrix_model').setValue(splatInfo.node.getWorldTransform().data); + // Decompose model matrix into scale and rotation + const worldTransform = splatInfo.node.getWorldTransform(); + worldTransform.getScale(_modelScale); + _modelRotation.setFromMat4(worldTransform); + + // Ensure w positive for sqrt reconstruction + if (_modelRotation.w < 0) { + _modelRotation.mulScalar(-1); + } + + // set as uniforms + this._modelScaleData[0] = _modelScale.x; + this._modelScaleData[1] = _modelScale.y; + this._modelScaleData[2] = _modelScale.z; + this._modelRotationData[0] = _modelRotation.x; + this._modelRotationData[1] = _modelRotation.y; + this._modelRotationData[2] = _modelRotation.z; + this._modelRotationData[3] = _modelRotation.w; + + scope.resolve('matrix_model').setValue(worldTransform.data); + scope.resolve('model_scale').setValue(this._modelScaleData); + scope.resolve('model_rotation').setValue(this._modelRotationData); // Render the quad - QuadRender handles all the complex setup internally workBufferRenderInfo.quadRender.render(viewport); diff --git a/src/scene/gsplat-unified/gsplat-work-buffer.js b/src/scene/gsplat-unified/gsplat-work-buffer.js index 033852635af..4a07650c568 100644 --- a/src/scene/gsplat-unified/gsplat-work-buffer.js +++ b/src/scene/gsplat-unified/gsplat-work-buffer.js @@ -123,6 +123,10 @@ class GSplatWorkBuffer { // Detect compatible HDR format for color texture, fallback to RGBA16U if RGBA16F not supported this.colorTextureFormat = device.getRenderableHdrFormat([PIXELFORMAT_RGBA16F]) || PIXELFORMAT_RGBA16U; + // Work buffer textures format: + // - colorTexture (RGBA16F/RGBA16U): RGBA color with alpha + // - splatTexture0 (RGBA32U): modelCenter.xyz (3×32-bit floats as uint) + 2×16-bit covariance halfs (covA.z, covB.z) + // - splatTexture1 (RG32U): 4×16-bit covariance halfs packed as (covA.xy, covB.xy) this.colorTexture = this.createTexture('splatColor', this.colorTextureFormat, 1, 1); this.splatTexture0 = this.createTexture('splatTexture0', PIXELFORMAT_RGBA32U, 1, 1); this.splatTexture1 = this.createTexture('splatTexture1', PIXELFORMAT_RG32U, 1, 1); diff --git a/src/scene/gsplat/gsplat-instance.js b/src/scene/gsplat/gsplat-instance.js index cc50fba97c5..119b8dd9f23 100644 --- a/src/scene/gsplat/gsplat-instance.js +++ b/src/scene/gsplat/gsplat-instance.js @@ -18,8 +18,6 @@ import { BLEND_NONE, BLEND_PREMULTIPLIED } from '../constants.js'; const mat = new Mat4(); const cameraPosition = new Vec3(); const cameraDirection = new Vec3(); -const viewport = [0, 0]; - /** @ignore */ class GSplatInstance { /** @type {GSplatResourceBase} */ @@ -59,6 +57,7 @@ class GSplatInstance { * @param {object} [options] - Options for the instance. * @param {ShaderMaterial|null} [options.material] - The material instance. * @param {boolean} [options.highQualitySH] - Whether to use the high quality or the approximate spherical harmonic calculation. Only applies to SOGS data. + * @param {import('../scene.js').Scene} [options.scene] - The scene to fire sort timing events on. */ constructor(resource, options = {}) { this.resource = resource; @@ -109,7 +108,7 @@ class GSplatInstance { const chunks = resource.chunks?.slice(); // create sorter - this.sorter = new GSplatSorter(); + this.sorter = new GSplatSorter(options.scene); this.sorter.init(this.orderTexture, centers, chunks); this.sorter.on('updated', (count) => { // limit splat render count to exclude those behind the camera @@ -183,23 +182,6 @@ class GSplatInstance { material.depthWrite = !!options.dither; } - updateViewport(cameraNode) { - const camera = cameraNode?.camera; - const renderTarget = camera?.renderTarget; - const { width, height } = renderTarget ?? this.resource.device; - - viewport[0] = width; - viewport[1] = height; - - // adjust viewport for stereoscopic VR sessions - const xr = camera?.camera?.xr; - if (xr?.active && xr.views.list.length === 2) { - viewport[0] *= 0.5; - } - - this.material.setParameter('viewport', viewport); - } - /** * Sorts the GS vertices based on the given camera. * @param {GraphNode} cameraNode - The camera node used for sorting. @@ -222,8 +204,6 @@ class GSplatInstance { this.sorter.setCamera(cameraPosition, cameraDirection); } } - - this.updateViewport(cameraNode); } update() { diff --git a/src/scene/gsplat/gsplat-resource-base.js b/src/scene/gsplat/gsplat-resource-base.js index 3e948a1097f..683b2fb65bb 100644 --- a/src/scene/gsplat/gsplat-resource-base.js +++ b/src/scene/gsplat/gsplat-resource-base.js @@ -49,6 +49,12 @@ class GSplatResourceBase { /** @type {Map} */ workBufferRenderInfos = new Map(); + /** + * @type {number} + * @private + */ + _refCount = 0; + constructor(device, gsplatData) { this.device = device; this.gsplatData = gsplatData; @@ -75,6 +81,39 @@ class GSplatResourceBase { this.workBufferRenderInfos.clear(); } + /** + * Increments the reference count. + * + * @ignore + */ + incRefCount() { + this._refCount++; + } + + /** + * Decrements the reference count. + * + * @ignore + */ + decRefCount() { + this._refCount--; + } + + /** + * Gets the current reference count. This represents how many times this resource is currently + * being used internally by the engine. For {@link GSplatComponent#asset|assets} assigned to + * {@link GSplatComponent#unified|unified} gsplat components, this tracks active usage during + * rendering and sorting operations. + * + * Resources should not be unloaded while the reference count is non-zero, as they are still + * in use by the rendering pipeline. + * + * @type {number} + */ + get refCount() { + return this._refCount; + } + /** * Get or create a QuadRender for rendering to work buffer. * diff --git a/src/scene/gsplat/gsplat-sogs-data.js b/src/scene/gsplat/gsplat-sogs-data.js index 78001f90f37..49e34557a73 100644 --- a/src/scene/gsplat/gsplat-sogs-data.js +++ b/src/scene/gsplat/gsplat-sogs-data.js @@ -22,6 +22,10 @@ import wgslGsplatPackingPS from '../shader-lib/wgsl/chunks/gsplat/frag/gsplatPac import glslSogsCentersPS from '../shader-lib/glsl/chunks/gsplat/frag/gsplatSogsCenters.js'; import wgslSogsCentersPS from '../shader-lib/wgsl/chunks/gsplat/frag/gsplatSogsCenters.js'; +/** + * @import { EventHandle } from '../../core/event-handle.js' + */ + const SH_C0 = 0.28209479177387814; const readImageDataAsync = (texture) => { @@ -180,6 +184,27 @@ class GSplatSogsData { packedShN; + /** + * URL of the asset, used for debugging texture names. + * + * @type {string} + */ + url = ''; + + /** + * Whether to use minimal memory mode (releases source textures after packing). + * + * @type {boolean} + */ + minimalMemory = false; + + /** + * Event handle for devicerestored listener (when minimalMemory is false). + * + * @type {EventHandle|null} + */ + deviceRestoredEvent = null; + /** * Cached centers array (x, y, z per splat), length = numSplats * 3. * @@ -191,6 +216,14 @@ class GSplatSogsData { // Marked when resource is destroyed, to abort any in-flight async preparation destroyed = false; + /** + * Cached number of spherical harmonics bands. + * + * @type {number} + * @private + */ + _shBands = 0; + _destroyGpuResources() { this.means_l?.destroy(); this.means_u?.destroy(); @@ -205,6 +238,10 @@ class GSplatSogsData { } destroy() { + // Remove devicerestored listener if it was registered + this.deviceRestoredEvent?.off(); + this.deviceRestoredEvent = null; + this.destroyed = true; this._destroyGpuResources(); } @@ -257,13 +294,7 @@ class GSplatSogsData { } get shBands() { - // sh palette has 64 sh entries per row. use width to calculate number of bands - const widths = { - 192: 1, // 64 * 3 - 512: 2, // 64 * 8 - 960: 3 // 64 * 15 - }; - return widths[this.sh_centroids?.width] ?? 0; + return this._shBands; } async decompress() { @@ -507,11 +538,21 @@ class GSplatSogsData { } async prepareGpuData() { - const { device, height, width } = this.means_l; + let device = this.means_l.device; + const { height, width } = this.means_l; + + if (this.destroyed || !device || device._destroyed) return; + + // Cache shBands from sh_centroids texture width before source textures may be destroyed + // sh palette has 64 sh entries per row: 192 = 1 band (64*3), 512 = 2 bands (64*8), 960 = 3 bands (64*15) + const shBandsWidths = { 192: 1, 512: 2, 960: 3 }; + this._shBands = shBandsWidths[this.sh_centroids?.width] ?? 0; + + // Include URL in texture name for debugging + const urlSuffix = this.url ? `_${this.url}` : ''; - if (this.destroyed || device._destroyed) return; // skip the rest if the resource was destroyed this.packedTexture = new Texture(device, { - name: 'sogsPackedTexture', + name: `sogsPackedTexture${urlSuffix}`, width, height, format: PIXELFORMAT_RGBA32U, @@ -519,7 +560,7 @@ class GSplatSogsData { }); this.packedSh0 = new Texture(device, { - name: 'sogsPackedSh0', + name: `sogsPackedSh0${urlSuffix}`, width, height, format: PIXELFORMAT_RGBA8, @@ -527,19 +568,23 @@ class GSplatSogsData { }); this.packedShN = this.sh_centroids && new Texture(device, { - name: 'sogsPackedShN', + name: `sogsPackedShN${urlSuffix}`, width: this.sh_centroids.width, height: this.sh_centroids.height, format: PIXELFORMAT_RGBA8, mipmaps: false }); - device.on('devicerestored', () => { - this.packGpuMemory(); - if (this.packedShN) { - this.packShMemory(); - } - }); + if (!this.minimalMemory) { + + // when context is restored, pack the gpu data again + this.deviceRestoredEvent = device.on('devicerestored', () => { + this.packGpuMemory(); + if (this.packedShN) { + this.packShMemory(); + } + }); + } // patch codebooks starting with a null entry ['scales', 'sh0', 'shN'].forEach((name) => { @@ -549,15 +594,37 @@ class GSplatSogsData { } }); - if (this.destroyed || device._destroyed) return; // skip the rest if the resource was destroyed + device = this.means_l?.device; + if (this.destroyed || !device || device._destroyed) return; await this.generateCenters(); - if (this.destroyed || device._destroyed) return; // skip the rest if the resource was destroyed + device = this.means_l?.device; + if (this.destroyed || !device || device._destroyed) return; this.packGpuMemory(); if (this.packedShN) { - if (this.destroyed || device._destroyed) return; // skip the rest if the resource was destroyed + device = this.means_l?.device; + if (this.destroyed || !device || device._destroyed) return; this.packShMemory(); } + + if (this.minimalMemory) { + // Release source textures to save memory + this.means_l?.destroy(); + this.means_u?.destroy(); + this.quats?.destroy(); + this.scales?.destroy(); + this.sh0?.destroy(); + this.sh_centroids?.destroy(); + this.sh_labels?.destroy(); + + this.means_l = null; + this.means_u = null; + this.quats = null; + this.scales = null; + this.sh0 = null; + this.sh_centroids = null; + this.sh_labels = null; + } } // temporary, for backwards compatibility diff --git a/src/scene/gsplat/gsplat-sort-worker.js b/src/scene/gsplat/gsplat-sort-worker.js index 01024379ddf..9e3e5a5dc04 100644 --- a/src/scene/gsplat/gsplat-sort-worker.js +++ b/src/scene/gsplat/gsplat-sort-worker.js @@ -44,6 +44,8 @@ function SortWorker() { const update = () => { if (!order || !centers || centers.length === 0 || !cameraPosition || !cameraDirection) return; + const sortStartTime = performance.now(); + const px = cameraPosition.x; const py = cameraPosition.y; const pz = cameraPosition.z; @@ -197,7 +199,8 @@ function SortWorker() { // send results myself.postMessage({ order: order.buffer, - count + count, + sortTime: performance.now() - sortStartTime }, [order.buffer]); order = null; diff --git a/src/scene/gsplat/gsplat-sorter.js b/src/scene/gsplat/gsplat-sorter.js index 185d2a50d15..e5813e733f0 100644 --- a/src/scene/gsplat/gsplat-sorter.js +++ b/src/scene/gsplat/gsplat-sorter.js @@ -10,12 +10,20 @@ class GSplatSorter extends EventHandler { centers; - constructor() { + scene; + + constructor(scene) { super(); + this.scene = scene ?? null; const messageHandler = (message) => { const msgData = message.data ?? message; + // Fire sortTime event on scene + if (this.scene && msgData.sortTime !== undefined) { + this.scene.fire('gsplat:sorted', msgData.sortTime); + } + const newOrder = msgData.order; const oldOrder = this.orderTexture._levels[0].buffer; diff --git a/src/scene/layer.js b/src/scene/layer.js index d9b9efd176d..206463215f0 100644 --- a/src/scene/layer.js +++ b/src/scene/layer.js @@ -178,6 +178,24 @@ class Layer { */ gsplatPlacements = []; + /** + * @type {Set} + * @ignore + */ + gsplatPlacementsSet = new Set(); + + /** + * @type {GSplatPlacement[]} + * @ignore + */ + gsplatShadowCasters = []; + + /** + * @type {Set} + * @ignore + */ + gsplatShadowCastersSet = new Set(); + /** * True if the gsplatPlacements array was modified. * @@ -494,8 +512,9 @@ class Layer { * @ignore */ addGSplatPlacement(placement) { - if (!this.gsplatPlacements.includes(placement)) { + if (!this.gsplatPlacementsSet.has(placement)) { this.gsplatPlacements.push(placement); + this.gsplatPlacementsSet.add(placement); this.gsplatPlacementsDirty = true; } } @@ -510,6 +529,36 @@ class Layer { const index = this.gsplatPlacements.indexOf(placement); if (index >= 0) { this.gsplatPlacements.splice(index, 1); + this.gsplatPlacementsSet.delete(placement); + this.gsplatPlacementsDirty = true; + } + } + + /** + * Adds a gsplat placement to this layer as a shadow caster. + * + * @param {GSplatPlacement} placement - A placement of a gsplat. + * @ignore + */ + addGSplatShadowCaster(placement) { + if (!this.gsplatShadowCastersSet.has(placement)) { + this.gsplatShadowCasters.push(placement); + this.gsplatShadowCastersSet.add(placement); + this.gsplatPlacementsDirty = true; + } + } + + /** + * Removes a gsplat placement from the shadow casters of this layer. + * + * @param {GSplatPlacement} placement - A placement of a gsplat. + * @ignore + */ + removeGSplatShadowCaster(placement) { + const index = this.gsplatShadowCasters.indexOf(placement); + if (index >= 0) { + this.gsplatShadowCasters.splice(index, 1); + this.gsplatShadowCastersSet.delete(placement); this.gsplatPlacementsDirty = true; } } diff --git a/src/scene/materials/standard-material-options-builder.js b/src/scene/materials/standard-material-options-builder.js index cad1503cb3d..09c220195e9 100644 --- a/src/scene/materials/standard-material-options-builder.js +++ b/src/scene/materials/standard-material-options-builder.js @@ -289,6 +289,8 @@ class StandardMaterialOptionsBuilder { options.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction; options.litOptions.dispersion = stdMat.dispersion > 0; options.litOptions.shadowCatcher = stdMat.shadowCatcher; + + options.litOptions.useVertexColorGamma = stdMat.vertexColorGamma; } _updateEnvOptions(options, stdMat, scene, cameraShaderParams) { diff --git a/src/scene/materials/standard-material-options.js b/src/scene/materials/standard-material-options.js index c3cc50909cb..36953643252 100644 --- a/src/scene/materials/standard-material-options.js +++ b/src/scene/materials/standard-material-options.js @@ -47,6 +47,8 @@ class StandardMaterialOptions { lightMapEncoding = 'linear'; + vertexColorGamma = false; + /** * If normal map contains X in RGB, Y in Alpha, and Z must be reconstructed. * diff --git a/src/scene/materials/standard-material-parameters.js b/src/scene/materials/standard-material-parameters.js index fb8f059fb6b..9c41de2247e 100644 --- a/src/scene/materials/standard-material-parameters.js +++ b/src/scene/materials/standard-material-parameters.js @@ -33,6 +33,8 @@ const standardMaterialParameterTypes = { ..._textureParameter('diffuseDetail', true, false), diffuseDetailMode: 'string', + vertexColorGamma: 'boolean', + specular: 'rgb', specularTint: 'boolean', ..._textureParameter('specular'), diff --git a/src/scene/materials/standard-material.js b/src/scene/materials/standard-material.js index d563a3415d9..c884c816812 100644 --- a/src/scene/materials/standard-material.js +++ b/src/scene/materials/standard-material.js @@ -523,6 +523,11 @@ const _tempColor = new Color(); * backfaces. * @property {boolean} shadowCatcher When enabled, the material will output accumulated directional * shadow value in linear space as the color. + * @property {boolean} vertexColorGamma When set to true, the vertex shader converts vertex colors + * from gamma to linear space to ensure correct interpolation in the fragment shader. This flag is + * provided for backwards compatibility, allowing users to mark their materials to handle vertex + * colors in gamma space. Defaults to false, which indicates that vertex colors are stored in + * linear space. * * @category Graphics */ @@ -1194,6 +1199,7 @@ function _defineMaterialProps() { _defineFlag('opacityDither', DITHER_NONE); _defineFlag('opacityShadowDither', DITHER_NONE); _defineFlag('shadowCatcher', false); + _defineFlag('vertexColorGamma', false); _defineTex2D('diffuse'); _defineTex2D('specular'); diff --git a/src/scene/renderer/render-pass-shadow-directional.js b/src/scene/renderer/render-pass-shadow-directional.js index 04eadd3da3e..521345bfcf5 100644 --- a/src/scene/renderer/render-pass-shadow-directional.js +++ b/src/scene/renderer/render-pass-shadow-directional.js @@ -10,7 +10,7 @@ import { SHADOWUPDATE_NONE, SHADOWUPDATE_THISFRAME } from '../constants.js'; class RenderPassShadowDirectional extends RenderPass { constructor(device, shadowRenderer, light, camera, allCascadesRendering) { super(device); - DebugHelper.setName(this, `${this.name}-${light._node.name}`); + DebugHelper.setName(this, `RenderPassShadowDir-${light._node.name}`); this.shadowRenderer = shadowRenderer; this.light = light; diff --git a/src/scene/renderer/renderer.js b/src/scene/renderer/renderer.js index 10aa5270ebe..e965beb33e6 100644 --- a/src/scene/renderer/renderer.js +++ b/src/scene/renderer/renderer.js @@ -10,7 +10,7 @@ import { BoundingSphere } from '../../core/shape/bounding-sphere.js'; import { CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL, BINDGROUP_MESH, BINDGROUP_VIEW, UNIFORM_BUFFER_DEFAULT_SLOT_NAME, - UNIFORMTYPE_MAT4, UNIFORMTYPE_MAT3, UNIFORMTYPE_VEC3, UNIFORMTYPE_VEC2, UNIFORMTYPE_FLOAT, UNIFORMTYPE_INT, + UNIFORMTYPE_MAT4, UNIFORMTYPE_MAT3, UNIFORMTYPE_VEC4, UNIFORMTYPE_VEC3, UNIFORMTYPE_VEC2, UNIFORMTYPE_FLOAT, UNIFORMTYPE_INT, SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT, CULLFACE_BACK, CULLFACE_FRONT, CULLFACE_NONE, BINDGROUP_MESH_UB @@ -236,6 +236,8 @@ class Renderer { this.farClipId = scope.resolve('camera_far'); this.cameraParams = new Float32Array(4); this.cameraParamsId = scope.resolve('camera_params'); + this.viewportSize = new Float32Array(4); + this.viewportSizeId = scope.resolve('viewport_size'); this.viewIndexId = scope.resolve('view_index'); this.viewIndexId.setValue(0); @@ -435,6 +437,23 @@ class Renderer { this.cameraParams[3] = camera.projection === PROJECTION_ORTHOGRAPHIC ? 1 : 0; this.cameraParamsId.setValue(this.cameraParams); + // viewport size + let viewportWidth = target ? target.width : this.device.width; + let viewportHeight = target ? target.height : this.device.height; + viewportWidth *= camera.rect.z; + viewportHeight *= camera.rect.w; + + // adjust viewport for stereoscopic VR sessions + if (camera.xr?.active && camera.xr.views.list.length === 2) { + viewportWidth *= 0.5; + } + + this.viewportSize[0] = viewportWidth; + this.viewportSize[1] = viewportHeight; + this.viewportSize[2] = 1 / viewportWidth; + this.viewportSize[3] = 1 / viewportHeight; + this.viewportSizeId.setValue(this.viewportSize); + // exposure this.exposureId.setValue(this.scene.physicalUnits ? camera.getExposure() : this.scene.exposure); @@ -737,6 +756,7 @@ class Renderer { new UniformFormat('matrix_view3', UNIFORMTYPE_MAT3), new UniformFormat('cubeMapRotationMatrix', UNIFORMTYPE_MAT3), new UniformFormat('view_position', UNIFORMTYPE_VEC3), + new UniformFormat('viewport_size', UNIFORMTYPE_VEC4), new UniformFormat('skyboxIntensity', UNIFORMTYPE_FLOAT), new UniformFormat('exposure', UNIFORMTYPE_FLOAT), new UniformFormat('textureBias', UNIFORMTYPE_FLOAT), diff --git a/src/scene/renderer/shadow-renderer.js b/src/scene/renderer/shadow-renderer.js index f4f897049e9..1a7ed3b4d43 100644 --- a/src/scene/renderer/shadow-renderer.js +++ b/src/scene/renderer/shadow-renderer.js @@ -16,6 +16,7 @@ import { EVENT_PRECULL, LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, SHADER_SHADOW, + SHADOWCAMERA_NAME, SHADOWUPDATE_NONE, SHADOWUPDATE_THISFRAME, shadowTypeInfo } from '../constants.js'; @@ -114,7 +115,7 @@ class ShadowRenderer { // creates shadow camera for a light and sets up its constant properties static createShadowCamera(shadowType, type, face) { - const shadowCam = LightCamera.create('ShadowCamera', type, face); + const shadowCam = LightCamera.create(SHADOWCAMERA_NAME, type, face); const shadowInfo = shadowTypeInfo.get(shadowType); Debug.assert(shadowInfo); diff --git a/src/scene/shader-lib/glsl/chunks/common/frag/pick.js b/src/scene/shader-lib/glsl/chunks/common/frag/pick.js index 3ba880b9c76..4e2546afdce 100644 --- a/src/scene/shader-lib/glsl/chunks/common/frag/pick.js +++ b/src/scene/shader-lib/glsl/chunks/common/frag/pick.js @@ -7,4 +7,12 @@ vec4 getPickOutput() { uvec4 col = (uvec4(meshInstanceId) >> shifts) & uvec4(0xff); return vec4(col) * inv; } + +#ifdef DEPTH_PICK_PASS + #include "floatAsUintPS" + + vec4 getPickDepth() { + return float2uint(gl_FragCoord.z); + } +#endif `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplat.js b/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplat.js index dde1ffc470f..7f44d466a79 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplat.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplat.js @@ -45,7 +45,10 @@ void main(void) { #ifdef PICK_PASS - gl_FragColor = getPickOutput(); + pcFragColor0 = getPickOutput(); + #ifdef DEPTH_PICK_PASS + pcFragColor1 = getPickDepth(); + #endif #elif SHADOW_PASS diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js b/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js index 95a6bd52a32..6402ffef752 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js @@ -9,8 +9,6 @@ export default /* glsl */` #include "gsplatQuatToMat3VS" #include "gsplatSourceFormatVS" -uniform mat4 uTransform; - uniform int uStartLine; // Start row in destination texture uniform int uViewportWidth; // Width of the destination viewport in pixels @@ -24,6 +22,10 @@ uniform vec3 uColorMultiply; // number of splats uniform int uActiveSplats; +// pre-computed model matrix decomposition +uniform vec3 model_scale; +uniform vec4 model_rotation; // (x,y,z,w) format + void main(void) { // local fragment coordinates (within the viewport) ivec2 localFragCoords = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y) - uStartLine); @@ -66,25 +68,27 @@ void main(void) { source.id = uint(originalIndex); source.uv = ivec2(source.id % srcSize, source.id / srcSize); - // read and transform center + // read center in local space vec3 modelCenter = readCenter(source); - modelCenter = (uTransform * vec4(modelCenter, 1.0)).xyz; + + // compute world-space center for storage + vec3 worldCenter = (matrix_model * vec4(modelCenter, 1.0)).xyz; SplatCenter center; initCenter(modelCenter, center); - // read and transform covariance - vec3 covA, covB; - readCovariance(source, covA, covB); + // Get source rotation and scale + // getRotation() returns (w,x,y,z) format, convert to (x,y,z,w) for quatMul + vec4 srcRotation = getRotation().yzwx; + vec3 srcScale = getScale(); - mat3 C = mat3( - covA.x, covA.y, covA.z, - covA.y, covB.x, covB.y, - covA.z, covB.y, covB.z - ); - mat3 linear = mat3(uTransform); - mat3 Ct = linear * C * transpose(linear); - covA = Ct[0]; - covB = vec3(Ct[1][1], Ct[1][2], Ct[2][2]); + // Combine: world = model * source (both in x,y,z,w format) + vec4 worldRotation = quatMul(model_rotation, srcRotation); + // Ensure w is positive so sqrt() reconstruction works correctly + // (quaternions q and -q represent the same rotation) + if (worldRotation.w < 0.0) { + worldRotation = -worldRotation; + } + vec3 worldScale = model_scale * srcScale; // read color vec4 color = readColor(source); @@ -120,8 +124,9 @@ void main(void) { pcFragColor0 = color; #endif #ifndef GSPLAT_COLOR_ONLY - pcFragColor1 = uvec4(floatBitsToUint(modelCenter.x), floatBitsToUint(modelCenter.y), floatBitsToUint(modelCenter.z), packHalf2x16(vec2(covA.z, covB.z))); - pcFragColor2 = uvec2(packHalf2x16(covA.xy), packHalf2x16(covB.xy)); + // Store rotation (xyz, w derived) and scale as 6 half-floats + pcFragColor1 = uvec4(floatBitsToUint(worldCenter.x), floatBitsToUint(worldCenter.y), floatBitsToUint(worldCenter.z), packHalf2x16(worldRotation.xy)); + pcFragColor2 = uvec2(packHalf2x16(vec2(worldRotation.z, worldScale.x)), packHalf2x16(worldScale.yz)); #endif } } diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplat.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplat.js index 8f4b25a63a8..05616ed6085 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplat.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplat.js @@ -1,6 +1,4 @@ export default /* glsl */` -#include "gsplatHelpersVS" -#include "gsplatCustomizeVS" #include "gsplatCommonVS" varying mediump vec2 gaussianUV; @@ -29,16 +27,13 @@ void main(void) { return; } - #ifdef GSPLAT_WORKBUFFER_DATA - loadSplatTextures(source); - #endif - vec3 modelCenter = readCenter(source); SplatCenter center; center.modelCenterOriginal = modelCenter; modifyCenter(modelCenter); + modifySplatCenter(modelCenter); center.modelCenterModified = modelCenter; if (!initCenter(modelCenter, center)) { @@ -76,6 +71,7 @@ void main(void) { #endif modifyColor(modelCenter, clr); + modifySplatColor(modelCenter, clr); clipCorner(corner, clr.w); diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCenter.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCenter.js index 4e742f55aa7..ba74895df3b 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCenter.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCenter.js @@ -1,19 +1,21 @@ export default /* glsl */` uniform mat4 matrix_model; uniform mat4 matrix_view; +uniform vec4 camera_params; // 1 / far, far, near, isOrtho #ifndef GSPLAT_CENTER_NOPROJ uniform mat4 matrix_projection; #endif // project the model space gaussian center to view and clip space -bool initCenter(vec3 modelCenter, out SplatCenter center) { +bool initCenter(vec3 modelCenter, inout SplatCenter center) { mat4 modelView = matrix_view * matrix_model; vec4 centerView = modelView * vec4(modelCenter, 1.0); #ifndef GSPLAT_CENTER_NOPROJ - // early out if splat is behind the camera - if (centerView.z > 0.0) { + // early out if splat is behind the camera (perspective only) + // orthographic projections don't need this check as frustum culling handles it + if (camera_params.w != 1.0 && centerView.z > 0.0) { return false; } diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCommon.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCommon.js index cb7dd534cb2..2ca10ea83ab 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCommon.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCommon.js @@ -1,4 +1,7 @@ export default /* glsl */` +#include "gsplatHelpersVS" +#include "gsplatCustomizeVS" +#include "gsplatModifyVS" #include "gsplatStructsVS" #include "gsplatEvalSHVS" diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCompressedData.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCompressedData.js index b01ed08eb01..ef55f0e165c 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCompressedData.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCompressedData.js @@ -1,4 +1,6 @@ export default /* glsl */` +#include "gsplatPackingPS" + uniform highp usampler2D packedTexture; uniform highp sampler2D chunkTexture; @@ -18,15 +20,6 @@ vec3 unpack111011(uint bits) { ); } -vec4 unpack8888(uint bits) { - return vec4( - float(bits >> 24u) / 255.0, - float((bits >> 16u) & 0xffu) / 255.0, - float((bits >> 8u) & 0xffu) / 255.0, - float(bits & 0xffu) / 255.0 - ); -} - const float norm = sqrt(2.0); vec4 unpackRotation(uint bits) { @@ -72,20 +65,4 @@ vec4 getRotation() { vec3 getScale() { return exp(mix(vec3(chunkDataB.zw, chunkDataC.x), chunkDataC.yzw, unpack111011(packedData.z))); } - -// given a rotation matrix and scale vector, compute 3d covariance A and B -void readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) { - mat3 rot = quatToMat3(getRotation()); - vec3 scale = getScale(); - - // M = S * R - mat3 M = transpose(mat3( - scale.x * rot[0], - scale.y * rot[1], - scale.z * rot[2] - )); - - covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); - covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); -} `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js index 400786ca579..b77f94a5465 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js @@ -1,6 +1,20 @@ export default /* glsl */` -uniform vec2 viewport; // viewport dimensions -uniform vec4 camera_params; // 1 / far, far, near, isOrtho +uniform vec4 viewport_size; // viewport width, height, 1/width, 1/height + +// compute 3d covariance from rotation (w,x,y,z format) and scale +void computeCovariance(vec4 rotation, vec3 scale, out vec3 covA, out vec3 covB) { + mat3 rot = quatToMat3(rotation); + + // M = S * R + mat3 M = transpose(mat3( + scale.x * rot[0], + scale.y * rot[1], + scale.z * rot[2] + )); + + covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); + covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); +} // calculate the clip-space offset from the center for this gaussian bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corner, vec3 covA, vec3 covB) { @@ -11,7 +25,7 @@ bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corne covA.z, covB.y, covB.z ); - float focal = viewport.x * center.projMat00; + float focal = viewport_size.x * center.projMat00; vec3 v = camera_params.w == 1.0 ? vec3(0.0, 0.0, 1.0) : center.view.xyz; float J1 = focal / v.z; @@ -43,7 +57,7 @@ bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corne float lambda2 = max(mid - radius, 0.1); // Use the smaller viewport dimension to limit the kernel size relative to the screen resolution. - float vmin = min(1024.0, min(viewport.x, viewport.y)); + float vmin = min(1024.0, min(viewport_size.x, viewport_size.y)); float l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin); float l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin); @@ -53,7 +67,7 @@ bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corne return false; } - vec2 c = center.proj.ww / viewport; + vec2 c = center.proj.ww * viewport_size.zw; // cull against frustum x/y axes if (any(greaterThan(abs(center.proj.xy) - vec2(max(l1, l2)) * c, center.proj.ww))) { @@ -72,9 +86,20 @@ bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corne // calculate the clip-space offset from the center for this gaussian bool initCorner(SplatSource source, SplatCenter center, out SplatCorner corner) { + // Get rotation and scale + vec4 rotation = getRotation().yzwx; // Convert (w,x,y,z) to (x,y,z,w) + vec3 scale = getScale(); + + // Hook: modify rotation and scale + modifySplatRotationScale(center.modelCenterOriginal, center.modelCenterModified, rotation, scale); + + // Compute covariance from (possibly modified) rotation and scale vec3 covA, covB; - readCovariance(source, covA, covB); + computeCovariance(rotation.wxyz, scale, covA, covB); // Convert back to (w,x,y,z) + + // Existing hook: modify covariance modifyCovariance(center.modelCenterOriginal, center.modelCenterModified, covA, covB); + return initCornerCov(source, center, corner, covA, covB); } `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatData.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatData.js index e44d5f104a2..2cad333757b 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatData.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatData.js @@ -4,12 +4,14 @@ uniform highp sampler2D transformB; // work values uint tAw; +vec4 tBcached; // read the model-space center of the gaussian vec3 readCenter(SplatSource source) { // read transform data uvec4 tA = texelFetch(transformA, source.uv, 0); tAw = tA.w; + tBcached = texelFetch(transformB, source.uv, 0); return uintBitsToFloat(tA.xyz); } @@ -17,21 +19,11 @@ vec4 unpackRotation(vec3 packed) { return vec4(packed.xyz, sqrt(max(0.0, 1.0 - dot(packed, packed)))); } -// sample covariance vectors -void readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) { - vec4 tB = texelFetch(transformB, source.uv, 0); - - mat3 rot = quatToMat3(unpackRotation(vec3(unpackHalf2x16(tAw), tB.w)).wxyz); - vec3 scale = tB.xyz; - - // M = S * R - mat3 M = transpose(mat3( - scale.x * rot[0], - scale.y * rot[1], - scale.z * rot[2] - )); +vec4 getRotation() { + return unpackRotation(vec3(unpackHalf2x16(tAw), tBcached.w)).wxyz; +} - covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); - covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); +vec3 getScale() { + return tBcached.xyz; } `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatHelpers.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatHelpers.js index fb618c97dac..15818a8816f 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatHelpers.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatHelpers.js @@ -19,4 +19,15 @@ void gsplatMakeRound(inout vec3 covA, inout vec3 covB, float size) { covA = vec3(s2, 0.0, 0.0); covB = vec3(s2, 0.0, s2); } + +// Make splat spherical by setting uniform scale +// Use size = 0.0 to hide the splat +void gsplatMakeSpherical(inout vec3 scale, float size) { + scale = vec3(size); +} + +// Get RMS size from scale vector +float gsplatGetSizeFromScale(vec3 scale) { + return sqrt((scale.x * scale.x + scale.y * scale.y + scale.z * scale.z) / 3.0); +} `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatModify.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatModify.js new file mode 100644 index 00000000000..65402fd4e1b --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatModify.js @@ -0,0 +1,34 @@ +export default /* glsl */` +// Modify splat center position +void modifySplatCenter(inout vec3 center) { + // Example: center.y += 1.0; // offset all splats up by 1 unit +} + +// Modify splat rotation and scale (more efficient than modifyCovariance) +// Parameters: +// originalCenter - center before any modification +// modifiedCenter - center after modifyCenter/modifySplatCenter +// rotation - quaternion (x,y,z,w) format +// scale - scale vector +void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) { + // Example to scale all splats by 2x: + // scale *= 2.0; + // + // Example to clamp size to a range: + // float size = gsplatGetSizeFromScale(scale); + // float newSize = clamp(size, 0.01, 0.5); + // scale *= newSize / size; + // + // Example to make splats spherical: + // float size = gsplatGetSizeFromScale(scale); + // gsplatMakeSpherical(scale, size * 0.5); + // + // To hide a splat: + // scale = vec3(0.0); +} + +// Modify splat color +void modifySplatColor(vec3 center, inout vec4 color) { + // Example: color.rgb *= 0.5; // darken all splats +} +`; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatQuatToMat3.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatQuatToMat3.js index e29cb79796b..0716564ac05 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatQuatToMat3.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatQuatToMat3.js @@ -18,4 +18,14 @@ mat3 quatToMat3(vec4 R) { 1.0 - Y.y - Z.z ); } + +// Quaternion multiplication: result = a * b +vec4 quatMul(vec4 a, vec4 b) { + return vec4( + a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, + a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z + ); +} `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatSogsData.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatSogsData.js index f51a1ae33ec..f373f4d723e 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatSogsData.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatSogsData.js @@ -27,31 +27,20 @@ vec3 readCenter(SplatSource source) { const float norm = sqrt(2.0); -// sample covariance vectors -void readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) { +vec4 getRotation() { // decode rotation quaternion vec3 qdata = unpack8888(packedSample.z).xyz; - vec3 sdata = unpack101010(packedSample.w >> 2u); - uint qmode = packedSample.w & 0x3u; vec3 abc = (qdata - 0.5) * norm; float d = sqrt(max(0.0, 1.0 - dot(abc, abc))); - vec4 quat = (qmode == 0u) ? vec4(d, abc) : - ((qmode == 1u) ? vec4(abc.x, d, abc.yz) : - ((qmode == 2u) ? vec4(abc.xy, d, abc.z) : vec4(abc, d))); - - mat3 rot = quatToMat3(quat); - vec3 scale = exp(mix(vec3(scales_mins), vec3(scales_maxs), sdata)); - - // M = S * R - mat3 M = transpose(mat3( - scale.x * rot[0], - scale.y * rot[1], - scale.z * rot[2] - )); + return (qmode == 0u) ? vec4(d, abc) : + ((qmode == 1u) ? vec4(abc.x, d, abc.yz) : + ((qmode == 2u) ? vec4(abc.xy, d, abc.z) : vec4(abc, d))); +} - covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); - covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); +vec3 getScale() { + vec3 sdata = unpack101010(packedSample.w >> 2u); + return exp(mix(vec3(scales_mins), vec3(scales_maxs), sdata)); } `; diff --git a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatWorkBuffer.js b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatWorkBuffer.js index f742e9034d0..8f9248f6506 100644 --- a/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatWorkBuffer.js +++ b/src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatWorkBuffer.js @@ -12,24 +12,24 @@ uniform highp usampler2D splatTexture1; uvec4 cachedSplatTexture0Data; uvec2 cachedSplatTexture1Data; -// load splat textures into globals to avoid redundant fetches -void loadSplatTextures(SplatSource source) { +// read the model-space center of the gaussian +vec3 readCenter(SplatSource source) { cachedSplatTexture0Data = texelFetch(splatTexture0, source.uv, 0); cachedSplatTexture1Data = texelFetch(splatTexture1, source.uv, 0).xy; + return vec3(uintBitsToFloat(cachedSplatTexture0Data.r), uintBitsToFloat(cachedSplatTexture0Data.g), uintBitsToFloat(cachedSplatTexture0Data.b)); } -// read the model-space center of the gaussian -vec3 readCenter(SplatSource source) { - return vec3(uintBitsToFloat(cachedSplatTexture0Data.r), uintBitsToFloat(cachedSplatTexture0Data.g), uintBitsToFloat(cachedSplatTexture0Data.b)); +vec4 getRotation() { + vec2 rotXY = unpackHalf2x16(cachedSplatTexture0Data.a); + vec2 rotZscaleX = unpackHalf2x16(cachedSplatTexture1Data.x); + vec3 rotXYZ = vec3(rotXY, rotZscaleX.x); + return vec4(rotXYZ, sqrt(max(0.0, 1.0 - dot(rotXYZ, rotXYZ)))).wxyz; } -// sample covariance vectors -void readCovariance(in SplatSource source, out vec3 cov_A, out vec3 cov_B) { - vec2 covAxy = unpackHalf2x16(cachedSplatTexture1Data.x); - vec2 covBxy = unpackHalf2x16(cachedSplatTexture1Data.y); - vec2 covAzBz = unpackHalf2x16(cachedSplatTexture0Data.a); - cov_A = vec3(covAxy, covAzBz.x); - cov_B = vec3(covBxy, covAzBz.y); +vec3 getScale() { + vec2 rotZscaleX = unpackHalf2x16(cachedSplatTexture1Data.x); + vec2 scaleYZ = unpackHalf2x16(cachedSplatTexture1Data.y); + return vec3(rotZscaleX.y, scaleYZ); } vec4 readColor(in SplatSource source) { diff --git a/src/scene/shader-lib/glsl/chunks/lit/frag/pass-other/litOtherMain.js b/src/scene/shader-lib/glsl/chunks/lit/frag/pass-other/litOtherMain.js index 068edad9cd0..151a918be8a 100644 --- a/src/scene/shader-lib/glsl/chunks/lit/frag/pass-other/litOtherMain.js +++ b/src/scene/shader-lib/glsl/chunks/lit/frag/pass-other/litOtherMain.js @@ -16,7 +16,10 @@ void main(void) { evaluateFrontend(); #ifdef PICK_PASS - gl_FragColor = getPickOutput(); + pcFragColor0 = getPickOutput(); + #ifdef DEPTH_PICK_PASS + pcFragColor1 = getPickDepth(); + #endif #endif #ifdef PREPASS_PASS diff --git a/src/scene/shader-lib/glsl/chunks/lit/frag/refractionDynamic.js b/src/scene/shader-lib/glsl/chunks/lit/frag/refractionDynamic.js index c2a71d6b560..b1a46e151e8 100644 --- a/src/scene/shader-lib/glsl/chunks/lit/frag/refractionDynamic.js +++ b/src/scene/shader-lib/glsl/chunks/lit/frag/refractionDynamic.js @@ -18,6 +18,11 @@ vec3 evalRefractionColor(vec3 refractionVector, float gloss, float refractionInd float refractionLod = log2(uScreenSize.x) * iorToRoughness; vec3 refraction = texture2DLod(uSceneColorMap, uv, refractionLod).rgb; + // Convert from gamma to linear space if needed + #ifdef SCENE_COLORMAP_GAMMA + refraction = decodeGamma(refraction); + #endif + return refraction; } @@ -71,7 +76,7 @@ void addRefraction( } else { - transmittance = refraction; + transmittance = vec3(1.0); } // Apply fresnel effect on refraction diff --git a/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js b/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js index 6b6220f824e..06aced88854 100644 --- a/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js +++ b/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js @@ -63,6 +63,15 @@ mat4 dModelMatrix; #include "litUserCodeVS" +#ifdef VERTEX_COLOR + vec3 decodeGamma(vec3 raw) { + return pow(raw, vec3(2.2)); + } + vec4 gammaCorrectInput(vec4 color) { + return vec4(decodeGamma(color.xyz), color.w); + } +#endif + void main(void) { #include "litUserMainStartVS" @@ -102,7 +111,11 @@ void main(void) { #include "uvTransformVS, UV_TRANSFORMS_COUNT" #ifdef VERTEX_COLOR - vVertexColor = vertex_color; + #ifdef STD_VERTEX_COLOR_GAMMA + vVertexColor = gammaCorrectInput(vertex_color); + #else + vVertexColor = vertex_color; + #endif #endif #ifdef LINEAR_DEPTH diff --git a/src/scene/shader-lib/glsl/chunks/radix-sort/radix-sort-count.js b/src/scene/shader-lib/glsl/chunks/radix-sort/radix-sort-count.js new file mode 100644 index 00000000000..0029140419c --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/radix-sort/radix-sort-count.js @@ -0,0 +1,111 @@ +// Count digits shader - Pass 0 of radix sort +// Counts how many elements in each group have a specific digit value +// +// Variants: +// - SOURCE_LINEAR: Read from linear-layout source texture (first pass) +// - (default): Read from Morton-layout internal texture (subsequent passes) +export default /* glsl */` +uniform highp usampler2D keysTexture; + +uniform int bitsPerStep; +uniform int groupSize; +uniform int elementCount; +uniform int imageElementsLog2; +uniform int currentBit; + +varying vec2 uv0; + +// Morton code functions for Z-order curve indexing +uint interleaveWithZero(uint word) { + word = (word ^ (word << 8u)) & 0x00ff00ffu; + word = (word ^ (word << 4u)) & 0x0f0f0f0fu; + word = (word ^ (word << 2u)) & 0x33333333u; + word = (word ^ (word << 1u)) & 0x55555555u; + return word; +} + +uint deinterleaveWithZero(uint word) { + word &= 0x55555555u; + word = (word | (word >> 1u)) & 0x33333333u; + word = (word | (word >> 2u)) & 0x0f0f0f0fu; + word = (word | (word >> 4u)) & 0x00ff00ffu; + word = (word | (word >> 8u)) & 0x0000ffffu; + return word; +} + +ivec2 indexToUV(uint index) { + return ivec2(deinterleaveWithZero(index), deinterleaveWithZero(index >> 1u)); +} + +uint uvToIndex(ivec2 uv) { + return interleaveWithZero(uint(uv.x)) | (interleaveWithZero(uint(uv.y)) << 1u); +} + +void main() { + // Get current pixel position + ivec2 pixel = ivec2(gl_FragCoord.xy); + uint morton = uvToIndex(pixel); + + // Calculate which digit and which group this pixel represents + uint elementsLog2 = uint(imageElementsLog2); + uint groupsLog2 = elementsLog2 - uint(groupSize); + uint digitIndex = morton >> groupsLog2; + uint keyIndex = (morton - (digitIndex << groupsLog2)) << uint(groupSize); + + // Out of bounds check + if (keyIndex >= uint(elementCount)) { + pcFragColor0 = 0.0; + return; + } + + // Count how many elements in this group have the target digit + // Vectorized: process 4 elements at a time using uvec4/bvec4 + uint count = 0u; + uint mask = (1u << uint(bitsPerStep)) - 1u; + uint cBit = uint(currentBit); + uvec4 digitIdx4 = uvec4(digitIndex); + uvec4 mask4 = uvec4(mask); + const uvec4 QUAD_OFFSETS = uvec4(0u, 1u, 2u, 3u); + + #ifdef SOURCE_LINEAR + uint sw = uint(textureSize(keysTexture, 0).x); + #define COUNT_QUAD(base) { \ + uvec4 mi4 = (keyIndex + base) + QUAD_OFFSETS; \ + uvec4 y4 = mi4 / sw; \ + uvec4 x4 = mi4 - y4 * sw; \ + uvec4 keys = uvec4( \ + texelFetch(keysTexture, ivec2(x4.x, y4.x), 0).r, \ + texelFetch(keysTexture, ivec2(x4.y, y4.y), 0).r, \ + texelFetch(keysTexture, ivec2(x4.z, y4.z), 0).r, \ + texelFetch(keysTexture, ivec2(x4.w, y4.w), 0).r \ + ); \ + uvec4 digits = (keys >> cBit) & mask4; \ + uvec4 m4 = uvec4(equal(digits, digitIdx4)); \ + count += m4.x + m4.y + m4.z + m4.w; \ + } + #else + #define COUNT_QUAD(base) { \ + uvec4 mi4 = (keyIndex + base) + QUAD_OFFSETS; \ + uvec4 keys = uvec4( \ + texelFetch(keysTexture, indexToUV(mi4.x), 0).r, \ + texelFetch(keysTexture, indexToUV(mi4.y), 0).r, \ + texelFetch(keysTexture, indexToUV(mi4.z), 0).r, \ + texelFetch(keysTexture, indexToUV(mi4.w), 0).r \ + ); \ + uvec4 digits = (keys >> cBit) & mask4; \ + uvec4 m4 = uvec4(equal(digits, digitIdx4)); \ + count += m4.x + m4.y + m4.z + m4.w; \ + } + #endif + + COUNT_QUAD(0u) + COUNT_QUAD(4u) + COUNT_QUAD(8u) + COUNT_QUAD(12u) + + #undef COUNT_QUAD + + // Output the count as raw float (R32F format) + pcFragColor0 = float(count); +} +`; diff --git a/src/scene/shader-lib/glsl/chunks/radix-sort/radix-sort-reorder.js b/src/scene/shader-lib/glsl/chunks/radix-sort/radix-sort-reorder.js new file mode 100644 index 00000000000..fab5cfe5f6a --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/radix-sort/radix-sort-reorder.js @@ -0,0 +1,217 @@ +// Binary search reorder shader - Pass 1 of radix sort +// Uses mipmap traversal for O(log n) lookup instead of O(n) linear search +// MRT version: reads from keys + indices textures, writes to two outputs +// +// Variants: +// - SOURCE_LINEAR: Read from linear-layout source texture (first pass) +// - (default): Read from Morton-layout internal texture (subsequent passes) +export default /* glsl */` +uniform highp usampler2D keysTexture; +#ifdef SOURCE_LINEAR + #define FIRST_PASS +#else + uniform highp usampler2D indicesTexture; +#endif + +uniform highp sampler2D prefixSums; +uniform int bitsPerStep; +uniform int groupSize; +uniform int elementCount; +uniform int imageElementsLog2; +uniform int currentBit; +uniform int imageSize; + +varying vec2 uv0; + +// Morton code functions for Z-order curve indexing +uint interleaveWithZero(uint word) { + word = (word ^ (word << 8u)) & 0x00ff00ffu; + word = (word ^ (word << 4u)) & 0x0f0f0f0fu; + word = (word ^ (word << 2u)) & 0x33333333u; + word = (word ^ (word << 1u)) & 0x55555555u; + return word; +} + +uint deinterleaveWithZero(uint word) { + word &= 0x55555555u; + word = (word | (word >> 1u)) & 0x33333333u; + word = (word | (word >> 2u)) & 0x0f0f0f0fu; + word = (word | (word >> 4u)) & 0x00ff00ffu; + word = (word | (word >> 8u)) & 0x0000ffffu; + return word; +} + +ivec2 indexToUV(uint index) { + return ivec2(deinterleaveWithZero(index), deinterleaveWithZero(index >> 1u)); +} + +uint uvToIndex(ivec2 uv) { + return interleaveWithZero(uint(uv.x)) | (interleaveWithZero(uint(uv.y)) << 1u); +} + +// Count active texels at a given mip level +// R32F format stores raw counts, mipmaps store averages +// Multiply by 4^level to convert average back to sum +// Uses bit shift instead of pow() for performance +float countActiveTexels(ivec3 uv, ivec2 offset) { + // 4^level = 2^(level*2) = 1 << (level * 2) + float scale = float(1u << (uint(uv.z) * 2u)); + return scale * texelFetch(prefixSums, uv.xy + offset, uv.z).r; +} + +// Binary search through the mipmap hierarchy to find which source texel +// maps to the given destination index +ivec2 activeTexelIndexToUV(float prefixWidth, float index, out float activePrevTexelSum) { + float maxLod = round(log2(prefixWidth)); + ivec3 uv = ivec3(0, 0, int(maxLod)); + + float countTotal = countActiveTexels(uv, ivec2(0, 0)); + activePrevTexelSum = 0.0; + + // Out of bounds check + if (index >= countTotal) { + activePrevTexelSum = countTotal; + return ivec2(-1, -1); + } + + // Traverse down the mipmap hierarchy + while (uv.z >= 1) { + uv = ivec3(uv.xy * 2, uv.z - 1); + + float count00 = countActiveTexels(uv, ivec2(0, 0)); + float count01 = countActiveTexels(uv, ivec2(1, 0)); + float count10 = countActiveTexels(uv, ivec2(0, 1)); + + float sum00 = activePrevTexelSum + count00; + float sum01 = sum00 + count01; + float sum10 = sum01 + count10; + + bool in00 = index < sum00; + bool in01 = index < sum01; + bool in10 = index < sum10; + + if (in00) { + // Stay at (0,0) + } else if (in01) { + uv.xy += ivec2(1, 0); + activePrevTexelSum += count00; + } else if (in10) { + uv.xy += ivec2(0, 1); + activePrevTexelSum += count00 + count01; + } else { + uv.xy += ivec2(1, 1); + activePrevTexelSum += count00 + count01 + count10; + } + } + + return uv.xy; +} + +void main() { + ivec2 pixel = ivec2(gl_FragCoord.xy); + + #ifdef OUTPUT_LINEAR + // Linear index for output (simpler for consumers to read) + uint index = uint(pixel.y) * uint(imageSize) + uint(pixel.x); + #else + // Morton index for internal passes (better cache locality) + uint index = uvToIndex(pixel); + #endif + + // Out of bounds check + if (index >= uint(elementCount)) { + pcFragColor0 = uvec4(0xFFFFFFFFu, 0u, 0u, 1u); + pcFragColor1 = uvec4(0xFFFFFFFFu, 0u, 0u, 1u); + return; + } + + // Calculate prefix sum texture dimensions + float prefixWidth = float(imageSize * (1 << (bitsPerStep >> 1))) / float(1 << (groupSize >> 1)); + + // Binary search through mipmaps + float count; + ivec2 activePixel = activeTexelIndexToUV(prefixWidth, float(index), count); + + if (activePixel.x < 0) { + pcFragColor0 = uvec4(0xFFFFFFFFu, 0u, 0u, 1u); + pcFragColor1 = uvec4(0xFFFFFFFFu, 0u, 0u, 1u); + return; + } + + // Convert active pixel back to key index and digit + uint activeIndex = uvToIndex(activePixel); + uint elementsLog2 = uint(imageElementsLog2); + uint groupsLog2 = elementsLog2 - uint(groupSize); + uint digitIndex = activeIndex >> groupsLog2; + uint keyIndex = (activeIndex - (digitIndex << groupsLog2)) << uint(groupSize); + + // Linear search within the group - optimized with integer math and incremental coords + uint outKey = 0u; + uint mask = (1u << uint(bitsPerStep)) - 1u; + uint localIndexU = uint(float(index) - count); + uint localCountU = 0u; + uint foundMortonIndex = keyIndex; + + #ifdef SOURCE_LINEAR + // Compute starting (x,y) once - only 1 div/mod instead of 16 + uint sw = uint(textureSize(keysTexture, 0).x); + uint baseY = keyIndex / sw; + uint baseX = keyIndex - baseY * sw; + uint x = baseX; + uint y = baseY; + + for (uint i = 0u; i < 16u; ++i) { + ivec2 groupPixel = ivec2(int(x), int(y)); + outKey = texelFetch(keysTexture, groupPixel, 0).r; + + uint digit = (outKey >> uint(currentBit)) & mask; + + if (digit == digitIndex) { + localCountU++; + if (localCountU > localIndexU) { + foundMortonIndex = keyIndex + i; + break; + } + } + + // Advance to next pixel with wrap + x++; + if (x >= sw) { + x = 0u; + y++; + } + } + #else + // Morton layout - can't use simple x++ increment, but still use integer math + for (uint i = 0u; i < 16u; ++i) { + uint mortonIndex = keyIndex + i; + ivec2 groupPixel = indexToUV(mortonIndex); + outKey = texelFetch(keysTexture, groupPixel, 0).r; + + uint digit = (outKey >> uint(currentBit)) & mask; + + if (digit == digitIndex) { + localCountU++; + if (localCountU > localIndexU) { + foundMortonIndex = mortonIndex; + break; + } + } + } + #endif + + // Read indices after finding the match + #ifdef FIRST_PASS + // First pass: indices are implicitly [0,1,2,...], use index directly + uint outIndex = foundMortonIndex; + #else + // Subsequent passes: read from shuffled indices texture + ivec2 indicesPixel = indexToUV(foundMortonIndex); + uint outIndex = texelFetch(indicesTexture, indicesPixel, 0).r; + #endif + + // Output to two render targets (MRT): keys (uint) and indices (uint) + pcFragColor0 = uvec4(outKey, 0u, 0u, 1u); + pcFragColor1 = uvec4(outIndex, 0u, 0u, 1u); +} +`; diff --git a/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js b/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js index 0cbf75eb71b..46252da5809 100644 --- a/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js +++ b/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js @@ -20,7 +20,7 @@ void getAlbedo() { #endif #ifdef STD_DIFFUSE_VERTEX - dAlbedo *= gammaCorrectInput(saturate(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL})); + dAlbedo *= saturate(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js b/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js index bf1fb5e8e96..09c8afa2112 100644 --- a/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js +++ b/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js @@ -10,7 +10,7 @@ void getEmission() { #endif #ifdef STD_EMISSIVE_VERTEX - dEmission *= gammaCorrectInput(saturate(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL})); + dEmission *= saturate(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/glsl/collections/gsplat-chunks-glsl.js b/src/scene/shader-lib/glsl/collections/gsplat-chunks-glsl.js index 3893fee3db3..4c270b7f64a 100644 --- a/src/scene/shader-lib/glsl/collections/gsplat-chunks-glsl.js +++ b/src/scene/shader-lib/glsl/collections/gsplat-chunks-glsl.js @@ -6,6 +6,7 @@ import gsplatCompressedSHVS from '../chunks/gsplat/vert/gsplatCompressedSH.js'; import gsplatCustomizeVS from '../chunks/gsplat/vert/gsplatCustomize.js'; import gsplatEvalSHVS from '../chunks/gsplat/vert/gsplatEvalSH.js'; import gsplatHelpersVS from '../chunks/gsplat/vert/gsplatHelpers.js'; +import gsplatModifyVS from '../chunks/gsplat/vert/gsplatModify.js'; import gsplatQuatToMat3VS from '../chunks/gsplat/vert/gsplatQuatToMat3.js'; import gsplatSogsColorVS from '../chunks/gsplat/vert/gsplatSogsColor.js'; import gsplatSogsDataVS from '../chunks/gsplat/vert/gsplatSogsData.js'; @@ -32,6 +33,7 @@ export const gsplatChunksGLSL = { gsplatCustomizeVS, gsplatEvalSHVS, gsplatHelpersVS, + gsplatModifyVS, gsplatQuatToMat3VS, gsplatSogsColorVS, gsplatSogsDataVS, diff --git a/src/scene/shader-lib/programs/lit-shader-options.js b/src/scene/shader-lib/programs/lit-shader-options.js index 0e9ce905f65..a9b41390d30 100644 --- a/src/scene/shader-lib/programs/lit-shader-options.js +++ b/src/scene/shader-lib/programs/lit-shader-options.js @@ -83,6 +83,8 @@ class LitShaderOptions { vertexColors = false; + useVertexColorGamma = false; + lightMapEnabled = false; dirLightMapEnabled = false; diff --git a/src/scene/shader-lib/programs/lit-shader.js b/src/scene/shader-lib/programs/lit-shader.js index cf0e07259c1..334ff83c68a 100644 --- a/src/scene/shader-lib/programs/lit-shader.js +++ b/src/scene/shader-lib/programs/lit-shader.js @@ -104,7 +104,15 @@ class LitShader { // shader language const userChunks = options.shaderChunks; - this.shaderLanguage = (device.isWebGPU && allowWGSL && userChunks?.useWGSL) ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL; + this.shaderLanguage = (device.isWebGPU && allowWGSL && (!userChunks || userChunks.useWGSL)) ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL; + + if (device.isWebGPU && this.shaderLanguage === SHADERLANGUAGE_GLSL) { + if (!device.hasTranspilers) { + Debug.errorOnce('Cannot use GLSL shader on WebGPU without transpilers', { + litShader: this + }); + } + } // resolve custom chunk attributes this.attributes = { @@ -132,6 +140,7 @@ class LitShader { userChunkMap.forEach((chunk, chunkName) => { // extract attribute names from the used chunk + Debug.assert(chunk); for (const a in builtinAttributes) { if (builtinAttributes.hasOwnProperty(a) && chunk.indexOf(a) >= 0) { this.attributes[a] = builtinAttributes[a]; @@ -284,6 +293,9 @@ class LitShader { attributes.vertex_color = SEMANTIC_COLOR; vDefines.set('VERTEX_COLOR', true); varyings.set('vVertexColor', 'vec4'); + if (options.useVertexColorGamma) { + vDefines.set('STD_VERTEX_COLOR_GAMMA', ''); + } } if (options.useMsdf && options.msdfTextAttribute) { diff --git a/src/scene/shader-lib/programs/standard.js b/src/scene/shader-lib/programs/standard.js index 745b2f0a07d..a4a6a70cbef 100644 --- a/src/scene/shader-lib/programs/standard.js +++ b/src/scene/shader-lib/programs/standard.js @@ -88,11 +88,8 @@ class ShaderGeneratorStandard extends ShaderGenerator { return expression; } - _validateMapChunk(propName, chunkName, chunks) { + _validateMapChunk(code, propName, chunkName, chunks) { Debug.call(() => { - // strip comments from the chunk - const code = chunks.get(chunkName).replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1'); - const requiredChangeStrings = []; // Helper function to add a formatted change string if the old syntax is found @@ -170,7 +167,16 @@ class ShaderGeneratorStandard extends ShaderGenerator { // log errors if the chunk format is deprecated (format changed in engine 2.7) Debug.call(() => { if (chunkCode) { - this._validateMapChunk(propNameCaps, chunkName, chunks); + // strip comments from the chunk + const code = chunks.get(chunkName).replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1'); + + this._validateMapChunk(code, propNameCaps, chunkName, chunks); + + if (vertexColorOption) { + if (code.includes('gammaCorrectInputVec3(saturate3(vVertexColor.') || code.includes('gammaCorrectInput(saturate(vVertexColor.')) { + Debug.errorOnce(`Shader chunk ${chunkName} contains gamma correction code which is incompatible with vertexColorGamma=true. Please remove gamma correction calls from the chunk.`, { code: code }); + } + } } }); diff --git a/src/scene/shader-lib/shader-chunk-map.js b/src/scene/shader-lib/shader-chunk-map.js index dbdedba587a..52d0c3c67f0 100644 --- a/src/scene/shader-lib/shader-chunk-map.js +++ b/src/scene/shader-lib/shader-chunk-map.js @@ -1,5 +1,14 @@ +import { Debug } from '../../core/debug.js'; import { hashCode } from '../../core/hash.js'; +/** + * @typedef {object} ChunkValidation + * @property {string} [message] - Deprecation message to display. + * @property {function(string, string):void} [callback] - Validation callback receiving chunk name and code. + * @property {string} [defaultCodeGLSL] - Default GLSL code. If matches, no warning. + * @property {string} [defaultCodeWGSL] - Default WGSL code. If matches, no warning. + */ + /** * A collection of shader chunks, used by {@link ShaderChunks}. This is a map of shader chunk names * to their code. As this class extends `Map`, it can be used as a `Map` as well in addition to @@ -8,10 +17,29 @@ import { hashCode } from '../../core/hash.js'; * @category Graphics */ class ShaderChunkMap extends Map { + /** + * Reference to chunk validations map. + * + * @type {Map|undefined} + * @private + */ + _validations; + _keyDirty = false; _key = ''; + /** + * Create a new ShaderChunkMap instance. + * + * @param {Map} [validations] - Optional map of chunk validations. + * @ignore + */ + constructor(validations) { + super(); + this._validations = validations; + } + /** * Adds a new shader chunk with a specified name and shader source code to the Map. If an * element with the same name already exists, the element will be updated. @@ -21,6 +49,22 @@ class ShaderChunkMap extends Map { * @returns {this} The ShaderChunkMap instance. */ set(name, code) { + // Run validation if registered for this chunk + Debug.call(() => { + const validation = this._validations?.get(name); + if (validation) { + const isDefault = code === validation.defaultCodeGLSL || code === validation.defaultCodeWGSL; + if (!isDefault) { + if (validation.message) { + Debug.deprecated(validation.message); + } + if (validation.callback) { + validation.callback(name, code); + } + } + } + }); + if (!this.has(name) || this.get(name) !== code) { this.markDirty(); } diff --git a/src/scene/shader-lib/shader-chunks.js b/src/scene/shader-lib/shader-chunks.js index 3882cbbec27..2bffaf36cef 100644 --- a/src/scene/shader-lib/shader-chunks.js +++ b/src/scene/shader-lib/shader-chunks.js @@ -1,9 +1,11 @@ +import { Debug } from '../../core/debug.js'; import { SHADERLANGUAGE_GLSL } from '../../platform/graphics/constants.js'; import { DeviceCache } from '../../platform/graphics/device-cache.js'; import { ShaderChunkMap } from './shader-chunk-map.js'; /** * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + * @import { ChunkValidation } from './shader-chunk-map.js' */ const _chunksCache = new DeviceCache(); @@ -14,13 +16,21 @@ const _chunksCache = new DeviceCache(); * @category Graphics */ class ShaderChunks { + /** + * Static map of chunk validations shared by all instances. + * + * @type {Map} + * @private + */ + static _validations = new Map(); + /** * A map of shader chunks for GLSL. * * @type {ShaderChunkMap} * @ignore */ - glsl = new ShaderChunkMap(); + glsl = new ShaderChunkMap(ShaderChunks._validations); /** * A map of shader chunks for WGSL. @@ -28,7 +38,7 @@ class ShaderChunks { * @type {ShaderChunkMap} * @ignore */ - wgsl = new ShaderChunkMap(); + wgsl = new ShaderChunkMap(ShaderChunks._validations); /** * Returns a shader chunks map for the given device and shader language. @@ -44,6 +54,44 @@ class ShaderChunks { return shaderLanguage === SHADERLANGUAGE_GLSL ? cache.glsl : cache.wgsl; } + /** + * Register a validation for a shader chunk. When the chunk is set, the validation will be + * executed. This is useful for deprecation warnings or content validation. + * + * @param {string} name - The name of the shader chunk. + * @param {ChunkValidation} options - Validation options. + * @example + * // Deprecate an existing chunk - only warn when overridden with non-default code + * import { myChunksGLSL } from './glsl/collections/my-chunks-glsl.js'; + * import { myChunksWGSL } from './wgsl/collections/my-chunks-wgsl.js'; + * + * ShaderChunks.registerValidation('myChunkVS', { + * message: 'myChunkVS is deprecated. Use newChunkVS instead.', + * defaultCodeGLSL: myChunksGLSL.myChunkVS, + * defaultCodeWGSL: myChunksWGSL.myChunkVS + * }); + * @example + * // Warn for a removed chunk - any attempt to use it triggers warning + * ShaderChunks.registerValidation('removedChunkVS', { + * message: 'removedChunkVS has been removed. Use replacementChunkVS instead.' + * }); + * @example + * // Use callback for custom validation logic + * ShaderChunks.registerValidation('myChunkVS', { + * callback: (name, code) => { + * if (code.includes('gl_FragColor')) { + * Debug.error(`Chunk ${name} uses deprecated gl_FragColor. Use pcFragColor instead.`); + * } + * } + * }); + * @ignore + */ + static registerValidation(name, options) { + Debug.call(() => { + ShaderChunks._validations.set(name, options); + }); + } + /** * Specifies the API version of the shader chunks. * diff --git a/src/scene/shader-lib/wgsl/chunks/common/frag/pick.js b/src/scene/shader-lib/wgsl/chunks/common/frag/pick.js index 6d197bc1457..36bafaa09db 100644 --- a/src/scene/shader-lib/wgsl/chunks/common/frag/pick.js +++ b/src/scene/shader-lib/wgsl/chunks/common/frag/pick.js @@ -6,4 +6,13 @@ fn getPickOutput() -> vec4f { let shifts: vec4u = vec4u(16u, 8u, 0u, 24u); let col: vec4u = (vec4u(uniform.meshInstanceId) >> shifts) & vec4u(0xffu); return vec4f(col) * inv; -}`; +} + +#ifdef DEPTH_PICK_PASS + #include "floatAsUintPS" + + fn getPickDepth() -> vec4f { + return float2uint(pcPosition.z); + } +#endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplat.js b/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplat.js index cf5d719908c..a9fb981baf7 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplat.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplat.js @@ -52,6 +52,9 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput { #ifdef PICK_PASS output.color = getPickOutput(); + #ifdef DEPTH_PICK_PASS + output.color1 = getPickDepth(); + #endif #elif SHADOW_PASS diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js b/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js index 5bc748e0abf..c9a6141873b 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js @@ -9,8 +9,6 @@ export default /* wgsl */` #include "gsplatQuatToMat3VS" #include "gsplatSourceFormatVS" -uniform uTransform: mat4x4f; - uniform uStartLine: i32; // Start row in destination texture uniform uViewportWidth: i32; // Width of the destination viewport in pixels @@ -24,6 +22,10 @@ uniform uColorMultiply: vec3f; // number of splats uniform uActiveSplats: i32; +// pre-computed model matrix decomposition +uniform model_scale: vec3f; +uniform model_rotation: vec4f; // (x,y,z,w) format + @fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput { var output: FragmentOutput; @@ -67,26 +69,27 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput { source.id = u32(originalIndex); source.uv = vec2i(i32(source.id % srcSize), i32(source.id / srcSize)); - // read and transform center + // read center in local space var modelCenter = readCenter(&source); - modelCenter = (uniform.uTransform * vec4f(modelCenter, 1.0)).xyz; + + // compute world-space center for storage + let worldCenter = (uniform.matrix_model * vec4f(modelCenter, 1.0)).xyz; var center: SplatCenter; initCenter(modelCenter, ¢er); - // read and transform covariance - var covA: vec3f; - var covB: vec3f; - readCovariance(&source, &covA, &covB); - - let C = mat3x3f( - vec3f(covA.x, covA.y, covA.z), - vec3f(covA.y, covB.x, covB.y), - vec3f(covA.z, covB.y, covB.z) - ); - let linear = mat3x3f(uniform.uTransform[0].xyz, uniform.uTransform[1].xyz, uniform.uTransform[2].xyz); - let Ct = linear * C * transpose(linear); - covA = Ct[0]; - covB = vec3f(Ct[1][1], Ct[1][2], Ct[2][2]); + // Get source rotation and scale + // getRotation() returns (w,x,y,z) format, convert to (x,y,z,w) for quatMul + let srcRotation = getRotation().yzwx; + let srcScale = getScale(); + + // Combine: world = model * source (both in x,y,z,w format) + var worldRotation = quatMul(uniform.model_rotation, srcRotation); + // Ensure w is positive so sqrt() reconstruction works correctly + // (quaternions q and -q represent the same rotation) + if (worldRotation.w < 0.0) { + worldRotation = -worldRotation; + } + let worldScale = uniform.model_scale * srcScale; // read color var color = readColor(&source); @@ -110,8 +113,9 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput { // write out results output.color = color; #ifndef GSPLAT_COLOR_ONLY - output.color1 = vec4u(bitcast(modelCenter.x), bitcast(modelCenter.y), bitcast(modelCenter.z), pack2x16float(vec2f(covA.z, covB.z))); - output.color2 = vec2u(pack2x16float(covA.xy), pack2x16float(covB.xy)); + // Store rotation (xyz, w derived) and scale as 6 half-floats + output.color1 = vec4u(bitcast(worldCenter.x), bitcast(worldCenter.y), bitcast(worldCenter.z), pack2x16float(worldRotation.xy)); + output.color2 = vec2u(pack2x16float(vec2f(worldRotation.z, worldScale.x)), pack2x16float(worldScale.yz)); #endif } diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplat.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplat.js index be10c4381be..e6c75b5f4f5 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplat.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplat.js @@ -1,7 +1,4 @@ export default /* wgsl */` - -#include "gsplatHelpersVS" -#include "gsplatCustomizeVS" #include "gsplatCommonVS" varying gaussianUV: vec2f; @@ -34,16 +31,13 @@ fn vertexMain(input: VertexInput) -> VertexOutput { return output; } - #ifdef GSPLAT_WORKBUFFER_DATA - loadSplatTextures(&source); - #endif - var modelCenter: vec3f = readCenter(&source); var center: SplatCenter; center.modelCenterOriginal = modelCenter; modifyCenter(&modelCenter); + modifySplatCenter(&modelCenter); center.modelCenterModified = modelCenter; if (!initCenter(modelCenter, ¢er)) { @@ -82,6 +76,7 @@ fn vertexMain(input: VertexInput) -> VertexOutput { #endif modifyColor(modelCenter, &clr); + modifySplatColor(modelCenter, &clr); clipCorner(&corner, clr.w); diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCenter.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCenter.js index 1f42c41c7a1..19033c3c660 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCenter.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCenter.js @@ -1,6 +1,7 @@ export default /* wgsl */` uniform matrix_model: mat4x4f; uniform matrix_view: mat4x4f; +uniform camera_params: vec4f; // 1 / far, far, near, isOrtho #ifndef GSPLAT_CENTER_NOPROJ uniform matrix_projection: mat4x4f; #endif @@ -12,8 +13,9 @@ fn initCenter(modelCenter: vec3f, center: ptr) -> bool { #ifndef GSPLAT_CENTER_NOPROJ - // early out if splat is behind the camera - if (centerView.z > 0.0) { + // early out if splat is behind the camera (perspective only) + // orthographic projections don't need this check as frustum culling handles it + if (uniform.camera_params.w != 1.0 && centerView.z > 0.0) { return false; } diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCommon.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCommon.js index 1f0b354ccef..45011d98417 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCommon.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCommon.js @@ -1,4 +1,7 @@ export default /* wgsl */` +#include "gsplatHelpersVS" +#include "gsplatCustomizeVS" +#include "gsplatModifyVS" #include "gsplatStructsVS" #include "gsplatEvalSHVS" diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCompressedData.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCompressedData.js index 02b131291e7..0bb95dc9a38 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCompressedData.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCompressedData.js @@ -1,4 +1,6 @@ export default /* wgsl */` +#include "gsplatPackingPS" + var packedTexture: texture_2d; var chunkTexture: texture_2d; @@ -14,15 +16,6 @@ fn unpack111011(bits: u32) -> vec3f { return (vec3f((vec3(bits) >> vec3(21u, 11u, 0u)) & vec3(0x7ffu, 0x3ffu, 0x7ffu))) / vec3f(2047.0, 1023.0, 2047.0); } -fn unpack8888(bits: u32) -> vec4f { - return vec4f( - f32((bits >> 24u) & 0xffu), - f32((bits >> 16u) & 0xffu), - f32((bits >> 8u) & 0xffu), - f32(bits & 0xffu) - ) / 255.0; -} - const norm_const: f32 = sqrt(2.0); fn unpackRotation(bits: u32) -> vec4f { @@ -68,20 +61,4 @@ fn getRotation() -> vec4f { fn getScale() -> vec3f { return exp(mix(vec3f(chunkDataB.zw, chunkDataC.x), chunkDataC.yzw, unpack111011(packedData.z))); } - -// given a rotation matrix and scale vector, compute 3d covariance A and B -fn readCovariance(source: ptr, covA_ptr: ptr, covB_ptr: ptr) { - let rot = quatToMat3(getRotation()); - let scale = getScale(); - - // M = S * R - let M = transpose(mat3x3f( - scale.x * rot[0], - scale.y * rot[1], - scale.z * rot[2] - )); - - *covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); - *covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); -} `; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCorner.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCorner.js index ce307c8fb50..b07c1b038a8 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCorner.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCorner.js @@ -1,6 +1,20 @@ export default /* wgsl */` -uniform viewport: vec2f; // viewport dimensions -uniform camera_params: vec4f; // 1 / far, far, near, isOrtho +uniform viewport_size: vec4f; // viewport width, height, 1/width, 1/height + +// compute 3d covariance from rotation (w,x,y,z format) and scale +fn computeCovariance(rotation: vec4f, scale: vec3f, covA_ptr: ptr, covB_ptr: ptr) { + let rot = quatToMat3(rotation); + + // M = S * R + let M = transpose(mat3x3f( + scale.x * rot[0], + scale.y * rot[1], + scale.z * rot[2] + )); + + *covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); + *covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); +} // calculate the clip-space offset from the center for this gaussian fn initCornerCov(source: ptr, center: ptr, corner: ptr, covA: vec3f, covB: vec3f) -> bool { @@ -11,7 +25,7 @@ fn initCornerCov(source: ptr, center: ptr, center: ptr, center: ptr center.proj.ww)) { @@ -72,10 +86,21 @@ fn initCornerCov(source: ptr, center: ptr, center: ptr, corner: ptr) -> bool { + // Get rotation and scale + var rotation: vec4f = getRotation().yzwx; // Convert (w,x,y,z) to (x,y,z,w) + var scale: vec3f = getScale(); + + // Hook: modify rotation and scale + modifySplatRotationScale(center.modelCenterOriginal, center.modelCenterModified, &rotation, &scale); + + // Compute covariance from (possibly modified) rotation and scale var covA: vec3f; var covB: vec3f; - readCovariance(source, &covA, &covB); + computeCovariance(rotation.wxyz, scale, &covA, &covB); // Convert back to (w,x,y,z) + + // Existing hook: modify covariance modifyCovariance(center.modelCenterOriginal, center.modelCenterModified, &covA, &covB); + return initCornerCov(source, center, corner, covA, covB); } `; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatData.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatData.js index 25320cedbe6..94c0b8c7ec6 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatData.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatData.js @@ -4,12 +4,14 @@ var transformB: texture_2d; // work values var tAw: u32; +var tBcached: vec4f; // read the model-space center of the gaussian fn readCenter(source: ptr) -> vec3f { // read transform data let tA: vec4 = textureLoad(transformA, source.uv, 0); tAw = tA.w; + tBcached = textureLoad(transformB, source.uv, 0); return bitcast(tA.xyz); } @@ -17,21 +19,11 @@ fn unpackRotation(packed: vec3f) -> vec4f { return vec4f(packed.xyz, sqrt(max(0.0, 1.0 - dot(packed, packed)))); } -// sample covariance vectors -fn readCovariance(source: ptr, covA_ptr: ptr, covB_ptr: ptr) { - let tB: vec4f = textureLoad(transformB, source.uv, 0); - - let rot: mat3x3f = quatToMat3(unpackRotation(vec3f(unpack2x16float(bitcast(tAw)), tB.w)).wxyz); - let scale: vec3f = tB.xyz; - - // M = S * R - let M = transpose(mat3x3f( - scale.x * rot[0], - scale.y * rot[1], - scale.z * rot[2] - )); +fn getRotation() -> vec4f { + return unpackRotation(vec3f(unpack2x16float(tAw), tBcached.w)).wxyz; +} - *covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); - *covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); +fn getScale() -> vec3f { + return tBcached.xyz; } `; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatHelpers.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatHelpers.js index 949f8f1ac41..ad62b2c128c 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatHelpers.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatHelpers.js @@ -19,4 +19,15 @@ fn gsplatMakeRound(covA: ptr, covB: ptr, size: *covA = vec3f(s2, 0.0, 0.0); *covB = vec3f(s2, 0.0, s2); } + +// Make splat spherical by setting uniform scale +// Use size = 0.0 to hide the splat +fn gsplatMakeSpherical(scale: ptr, size: f32) { + *scale = vec3f(size); +} + +// Get RMS size from scale vector +fn gsplatGetSizeFromScale(scale: vec3f) -> f32 { + return sqrt((scale.x * scale.x + scale.y * scale.y + scale.z * scale.z) / 3.0); +} `; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatModify.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatModify.js new file mode 100644 index 00000000000..f93bbf769ec --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatModify.js @@ -0,0 +1,34 @@ +export default /* wgsl */` +// Modify splat center position +fn modifySplatCenter(center: ptr) { + // Example: *center.y += 1.0; // offset all splats up by 1 unit +} + +// Modify splat rotation and scale (more efficient than modifyCovariance) +// Parameters: +// originalCenter - center before any modification +// modifiedCenter - center after modifyCenter/modifySplatCenter +// rotation - quaternion (x,y,z,w) format +// scale - scale vector +fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr, scale: ptr) { + // Example to scale all splats by 2x: + // *scale *= 2.0; + // + // Example to clamp size to a range: + // let size = gsplatGetSizeFromScale(*scale); + // let newSize = clamp(size, 0.01, 0.5); + // *scale *= newSize / size; + // + // Example to make splats spherical: + // let size = gsplatGetSizeFromScale(*scale); + // gsplatMakeSpherical(scale, size * 0.5); + // + // To hide a splat: + // *scale = vec3f(0.0); +} + +// Modify splat color +fn modifySplatColor(center: vec3f, color: ptr) { + // Example: *color.rgb *= 0.5; // darken all splats +} +`; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatQuatToMat3.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatQuatToMat3.js index 665638fcb53..b3d3724655d 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatQuatToMat3.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatQuatToMat3.js @@ -12,4 +12,14 @@ fn quatToMat3(R: vec4) -> mat3x3 { Y.w + Z.x, Z.w - Y.x, 1.0 - Y.y - Z.z ); } + +// Quaternion multiplication: result = a * b +fn quatMul(a: vec4, b: vec4) -> vec4 { + return vec4( + a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, + a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z + ); +} `; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatSogsData.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatSogsData.js index 35a70c05d73..813a9557593 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatSogsData.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatSogsData.js @@ -26,11 +26,8 @@ fn readCenter(source: ptr) -> vec3f { const norm: f32 = sqrt(2.0); -// sample covariance vectors -fn readCovariance(source: ptr, covA_ptr: ptr, covB_ptr: ptr) { +fn getRotation() -> vec4f { let qdata = unpack8888(packedSample.z).xyz; - let sdata = unpack101010(packedSample.w >> 2u); - let qmode = packedSample.w & 0x3u; let abc = (qdata - 0.5) * norm; let d = sqrt(max(0.0, 1.0 - dot(abc, abc))); @@ -45,18 +42,11 @@ fn readCovariance(source: ptr, covA_ptr: ptr vec3f { + let sdata = unpack101010(packedSample.w >> 2u); + return exp(mix(vec3f(uniform.scales_mins), vec3f(uniform.scales_maxs), sdata)); } `; diff --git a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatWorkBuffer.js b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatWorkBuffer.js index 96815e89b72..c2d5f35a56f 100644 --- a/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatWorkBuffer.js +++ b/src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatWorkBuffer.js @@ -7,24 +7,24 @@ var splatColor: texture_2d; var cachedSplatTexture0Data: vec4u; var cachedSplatTexture1Data: vec2u; -// load splat textures into globals to avoid redundant fetches -fn loadSplatTextures(source: ptr) { +// read the model-space center of the gaussian +fn readCenter(source: ptr) -> vec3f { cachedSplatTexture0Data = textureLoad(splatTexture0, source.uv, 0); cachedSplatTexture1Data = textureLoad(splatTexture1, source.uv, 0).xy; + return vec3f(bitcast(cachedSplatTexture0Data.r), bitcast(cachedSplatTexture0Data.g), bitcast(cachedSplatTexture0Data.b)); } -// read the model-space center of the gaussian -fn readCenter(source: ptr) -> vec3f { - return vec3f(bitcast(cachedSplatTexture0Data.r), bitcast(cachedSplatTexture0Data.g), bitcast(cachedSplatTexture0Data.b)); +fn getRotation() -> vec4f { + let rotXY = unpack2x16float(cachedSplatTexture0Data.a); + let rotZscaleX = unpack2x16float(cachedSplatTexture1Data.x); + let rotXYZ = vec3f(rotXY, rotZscaleX.x); + return vec4f(rotXYZ, sqrt(max(0.0, 1.0 - dot(rotXYZ, rotXYZ)))).wxyz; } -// sample covariance vectors -fn readCovariance(source: ptr, cov_A: ptr, cov_B: ptr) { - let covAxy = unpack2x16float(cachedSplatTexture1Data.x); - let covBxy = unpack2x16float(cachedSplatTexture1Data.y); - let covAzBz = unpack2x16float(cachedSplatTexture0Data.a); - *cov_A = vec3f(covAxy, covAzBz.x); - *cov_B = vec3f(covBxy, covAzBz.y); +fn getScale() -> vec3f { + let rotZscaleX = unpack2x16float(cachedSplatTexture1Data.x); + let scaleYZ = unpack2x16float(cachedSplatTexture1Data.y); + return vec3f(rotZscaleX.y, scaleYZ); } fn readColor(source: ptr) -> vec4f { diff --git a/src/scene/shader-lib/wgsl/chunks/lit/frag/pass-other/litOtherMain.js b/src/scene/shader-lib/wgsl/chunks/lit/frag/pass-other/litOtherMain.js index 60e16905991..9d7adbd37b4 100644 --- a/src/scene/shader-lib/wgsl/chunks/lit/frag/pass-other/litOtherMain.js +++ b/src/scene/shader-lib/wgsl/chunks/lit/frag/pass-other/litOtherMain.js @@ -20,6 +20,9 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput { #ifdef PICK_PASS output.color = getPickOutput(); + #ifdef DEPTH_PICK_PASS + output.color1 = getPickDepth(); + #endif #endif #ifdef PREPASS_PASS diff --git a/src/scene/shader-lib/wgsl/chunks/lit/frag/refractionDynamic.js b/src/scene/shader-lib/wgsl/chunks/lit/frag/refractionDynamic.js index 83f754fbad5..5c509844d11 100644 --- a/src/scene/shader-lib/wgsl/chunks/lit/frag/refractionDynamic.js +++ b/src/scene/shader-lib/wgsl/chunks/lit/frag/refractionDynamic.js @@ -16,7 +16,12 @@ fn evalRefractionColor(refractionVector: vec3f, gloss: f32, refractionIndex: f32 // Use IOR and roughness to select mip let iorToRoughness: f32 = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0); let refractionLod: f32 = log2(uniform.uScreenSize.x) * iorToRoughness; - let refraction: vec3f = textureSampleLevel(uSceneColorMap, uSceneColorMapSampler, uv, refractionLod).rgb; + var refraction: vec3f = textureSampleLevel(uSceneColorMap, uSceneColorMapSampler, uv, refractionLod).rgb; + + // Convert from gamma to linear space if needed + #ifdef SCENE_COLORMAP_GAMMA + refraction = decodeGamma3(refraction); + #endif return refraction; } @@ -71,7 +76,7 @@ fn addRefraction( } else { - transmittance = refraction; + transmittance = vec3f(1.0); } // Apply fresnel effect on refraction diff --git a/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js b/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js index 0b9d8c30e33..96d209e0c71 100644 --- a/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js +++ b/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js @@ -66,6 +66,15 @@ var dModelMatrix: mat4x4f; #include "litUserCodeVS" +#ifdef VERTEX_COLOR + fn decodeGamma3(raw: vec3f) -> vec3f { + return pow(raw, vec3f(2.2)); + } + fn gammaCorrectInputVec4(color: vec4f) -> vec4f { + return vec4f(decodeGamma3(color.xyz), color.w); + } +#endif + @vertex fn vertexMain(input : VertexInput) -> VertexOutput { @@ -104,7 +113,11 @@ fn vertexMain(input : VertexInput) -> VertexOutput { #include "uvTransformVS, UV_TRANSFORMS_COUNT" #ifdef VERTEX_COLOR - output.vVertexColor = vertex_color; + #ifdef STD_VERTEX_COLOR_GAMMA + output.vVertexColor = gammaCorrectInputVec4(vertex_color); + #else + output.vVertexColor = vertex_color; + #endif #endif #ifdef LINEAR_DEPTH diff --git a/src/scene/shader-lib/wgsl/chunks/radix-sort/radix-sort-count.js b/src/scene/shader-lib/wgsl/chunks/radix-sort/radix-sort-count.js new file mode 100644 index 00000000000..2a5be130ef8 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/radix-sort/radix-sort-count.js @@ -0,0 +1,186 @@ +// Count digits shader - Pass 0 of radix sort +// Counts how many elements in each group have a specific digit value +// +// Variants: +// - SOURCE_LINEAR: Read from linear-layout source texture (first pass) +// - (default): Read from Morton-layout internal texture (subsequent passes) +export default /* wgsl */` +var keysTexture: texture_2d; + +uniform bitsPerStep: i32; +uniform groupSize: i32; +uniform elementCount: i32; +uniform imageElementsLog2: i32; +uniform currentBit: i32; + +varying uv0: vec2f; + +// Morton code functions for Z-order curve indexing +fn interleaveWithZero(word_in: u32) -> u32 { + var word = word_in; + word = (word ^ (word << 8u)) & 0x00ff00ffu; + word = (word ^ (word << 4u)) & 0x0f0f0f0fu; + word = (word ^ (word << 2u)) & 0x33333333u; + word = (word ^ (word << 1u)) & 0x55555555u; + return word; +} + +fn deinterleaveWithZero(word_in: u32) -> u32 { + var word = word_in & 0x55555555u; + word = (word | (word >> 1u)) & 0x33333333u; + word = (word | (word >> 2u)) & 0x0f0f0f0fu; + word = (word | (word >> 4u)) & 0x00ff00ffu; + word = (word | (word >> 8u)) & 0x0000ffffu; + return word; +} + +fn indexToUV(index: u32) -> vec2i { + return vec2i(i32(deinterleaveWithZero(index)), i32(deinterleaveWithZero(index >> 1u))); +} + +fn uvToIndex(uv: vec2i) -> u32 { + return interleaveWithZero(u32(uv.x)) | (interleaveWithZero(u32(uv.y)) << 1u); +} + +@fragment +fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + + // Get current pixel position + let pixel = vec2i(input.position.xy); + let morton = uvToIndex(pixel); + + // Calculate which digit and which group this pixel represents + let elementsLog2 = u32(uniform.imageElementsLog2); + let groupsLog2 = elementsLog2 - u32(uniform.groupSize); + let digitIndex = morton >> groupsLog2; + let keyIndex = (morton - (digitIndex << groupsLog2)) << u32(uniform.groupSize); + + // Out of bounds check + if (keyIndex >= u32(uniform.elementCount)) { + output.color = 0.0; + return output; + } + + // Count how many elements in this group have the target digit + // Vectorized: process 4 elements at a time using vec4u + var count: u32 = 0u; + let mask = (1u << u32(uniform.bitsPerStep)) - 1u; + let cBit = u32(uniform.currentBit); + let digitIdx4 = vec4u(digitIndex); + let mask4 = vec4u(mask); + let QUAD_OFFSETS = vec4u(0u, 1u, 2u, 3u); + + #ifdef SOURCE_LINEAR + let sw = i32(textureDimensions(keysTexture, 0).x); + + // Quad 0-3 + var mi4 = keyIndex + QUAD_OFFSETS; + var y4 = vec4i(mi4) / sw; + var x4 = vec4i(mi4) - y4 * sw; + var keys = vec4u( + textureLoad(keysTexture, vec2i(x4.x, y4.x), 0).r, + textureLoad(keysTexture, vec2i(x4.y, y4.y), 0).r, + textureLoad(keysTexture, vec2i(x4.z, y4.z), 0).r, + textureLoad(keysTexture, vec2i(x4.w, y4.w), 0).r + ); + var digits = (keys >> vec4u(cBit)) & mask4; + var m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + + // Quad 4-7 + mi4 = (keyIndex + 4u) + QUAD_OFFSETS; + y4 = vec4i(mi4) / sw; + x4 = vec4i(mi4) - y4 * sw; + keys = vec4u( + textureLoad(keysTexture, vec2i(x4.x, y4.x), 0).r, + textureLoad(keysTexture, vec2i(x4.y, y4.y), 0).r, + textureLoad(keysTexture, vec2i(x4.z, y4.z), 0).r, + textureLoad(keysTexture, vec2i(x4.w, y4.w), 0).r + ); + digits = (keys >> vec4u(cBit)) & mask4; + m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + + // Quad 8-11 + mi4 = (keyIndex + 8u) + QUAD_OFFSETS; + y4 = vec4i(mi4) / sw; + x4 = vec4i(mi4) - y4 * sw; + keys = vec4u( + textureLoad(keysTexture, vec2i(x4.x, y4.x), 0).r, + textureLoad(keysTexture, vec2i(x4.y, y4.y), 0).r, + textureLoad(keysTexture, vec2i(x4.z, y4.z), 0).r, + textureLoad(keysTexture, vec2i(x4.w, y4.w), 0).r + ); + digits = (keys >> vec4u(cBit)) & mask4; + m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + + // Quad 12-15 + mi4 = (keyIndex + 12u) + QUAD_OFFSETS; + y4 = vec4i(mi4) / sw; + x4 = vec4i(mi4) - y4 * sw; + keys = vec4u( + textureLoad(keysTexture, vec2i(x4.x, y4.x), 0).r, + textureLoad(keysTexture, vec2i(x4.y, y4.y), 0).r, + textureLoad(keysTexture, vec2i(x4.z, y4.z), 0).r, + textureLoad(keysTexture, vec2i(x4.w, y4.w), 0).r + ); + digits = (keys >> vec4u(cBit)) & mask4; + m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + #else + // Quad 0-3 + var mi4 = keyIndex + QUAD_OFFSETS; + var keys = vec4u( + textureLoad(keysTexture, indexToUV(mi4.x), 0).r, + textureLoad(keysTexture, indexToUV(mi4.y), 0).r, + textureLoad(keysTexture, indexToUV(mi4.z), 0).r, + textureLoad(keysTexture, indexToUV(mi4.w), 0).r + ); + var digits = (keys >> vec4u(cBit)) & mask4; + var m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + + // Quad 4-7 + mi4 = (keyIndex + 4u) + QUAD_OFFSETS; + keys = vec4u( + textureLoad(keysTexture, indexToUV(mi4.x), 0).r, + textureLoad(keysTexture, indexToUV(mi4.y), 0).r, + textureLoad(keysTexture, indexToUV(mi4.z), 0).r, + textureLoad(keysTexture, indexToUV(mi4.w), 0).r + ); + digits = (keys >> vec4u(cBit)) & mask4; + m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + + // Quad 8-11 + mi4 = (keyIndex + 8u) + QUAD_OFFSETS; + keys = vec4u( + textureLoad(keysTexture, indexToUV(mi4.x), 0).r, + textureLoad(keysTexture, indexToUV(mi4.y), 0).r, + textureLoad(keysTexture, indexToUV(mi4.z), 0).r, + textureLoad(keysTexture, indexToUV(mi4.w), 0).r + ); + digits = (keys >> vec4u(cBit)) & mask4; + m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + + // Quad 12-15 + mi4 = (keyIndex + 12u) + QUAD_OFFSETS; + keys = vec4u( + textureLoad(keysTexture, indexToUV(mi4.x), 0).r, + textureLoad(keysTexture, indexToUV(mi4.y), 0).r, + textureLoad(keysTexture, indexToUV(mi4.z), 0).r, + textureLoad(keysTexture, indexToUV(mi4.w), 0).r + ); + digits = (keys >> vec4u(cBit)) & mask4; + m4 = select(vec4u(0u), vec4u(1u), digits == digitIdx4); + count += m4.x + m4.y + m4.z + m4.w; + #endif + + // Output the count as raw float (R32F format) + output.color = f32(count); + return output; +} +`; diff --git a/src/scene/shader-lib/wgsl/chunks/radix-sort/radix-sort-reorder.js b/src/scene/shader-lib/wgsl/chunks/radix-sort/radix-sort-reorder.js new file mode 100644 index 00000000000..cb4ac0bec21 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/radix-sort/radix-sort-reorder.js @@ -0,0 +1,217 @@ +// Binary search reorder shader - Pass 1 of radix sort +// Uses mipmap traversal for O(log n) lookup instead of O(n) linear search +// MRT version: reads from keys + indices textures, writes to two outputs +// +// Variants: +// - SOURCE_LINEAR: Read from linear-layout source texture (first pass) +// - (default): Read from Morton-layout internal texture (subsequent passes) +export default /* wgsl */` +var keysTexture: texture_2d; +#ifdef SOURCE_LINEAR + #define FIRST_PASS +#else + var indicesTexture: texture_2d; +#endif + +var prefixSums: texture_2d; + +uniform bitsPerStep: i32; +uniform groupSize: i32; +uniform elementCount: i32; +uniform imageElementsLog2: i32; +uniform currentBit: i32; +uniform imageSize: i32; + +varying uv0: vec2f; + +// Morton code functions for Z-order curve indexing +fn interleaveWithZero(word_in: u32) -> u32 { + var word = word_in; + word = (word ^ (word << 8u)) & 0x00ff00ffu; + word = (word ^ (word << 4u)) & 0x0f0f0f0fu; + word = (word ^ (word << 2u)) & 0x33333333u; + word = (word ^ (word << 1u)) & 0x55555555u; + return word; +} + +fn deinterleaveWithZero(word_in: u32) -> u32 { + var word = word_in & 0x55555555u; + word = (word | (word >> 1u)) & 0x33333333u; + word = (word | (word >> 2u)) & 0x0f0f0f0fu; + word = (word | (word >> 4u)) & 0x00ff00ffu; + word = (word | (word >> 8u)) & 0x0000ffffu; + return word; +} + +fn indexToUV(index: u32) -> vec2i { + return vec2i(i32(deinterleaveWithZero(index)), i32(deinterleaveWithZero(index >> 1u))); +} + +fn uvToIndex(uv: vec2i) -> u32 { + return interleaveWithZero(u32(uv.x)) | (interleaveWithZero(u32(uv.y)) << 1u); +} + +// Count active texels at a given mip level +// Uses bit shift instead of pow() for performance +fn countActiveTexels(uv: vec3i, offset: vec2i) -> f32 { + // 4^level = 2^(level*2) = 1 << (level * 2) + let scale = f32(1u << (u32(uv.z) * 2u)); + return scale * textureLoad(prefixSums, uv.xy + offset, uv.z).r; +} + +// Binary search result structure +struct BinarySearchResult { + pixel: vec2i, + prefixSum: f32 +} + +// Binary search through the mipmap hierarchy +fn activeTexelIndexToUV(prefixWidth: f32, index: f32) -> BinarySearchResult { + var result: BinarySearchResult; + + let maxLod = i32(round(log2(prefixWidth))); + var uv = vec3i(0, 0, maxLod); + + let countTotal = countActiveTexels(uv, vec2i(0, 0)); + result.prefixSum = 0.0; + + if (index >= countTotal) { + result.prefixSum = countTotal; + result.pixel = vec2i(-1, -1); + return result; + } + + while (uv.z >= 1) { + uv = vec3i(uv.xy * 2, uv.z - 1); + + let count00 = countActiveTexels(uv, vec2i(0, 0)); + let count01 = countActiveTexels(uv, vec2i(1, 0)); + let count10 = countActiveTexels(uv, vec2i(0, 1)); + + let in00 = index < (result.prefixSum + count00); + let in01 = index < (result.prefixSum + count00 + count01); + let in10 = index < (result.prefixSum + count00 + count01 + count10); + + if (in00) { + // Stay at (0,0) + } else if (in01) { + uv = vec3i(uv.x + 1, uv.y, uv.z); + result.prefixSum += count00; + } else if (in10) { + uv = vec3i(uv.x, uv.y + 1, uv.z); + result.prefixSum += count00 + count01; + } else { + uv = vec3i(uv.x + 1, uv.y + 1, uv.z); + result.prefixSum += count00 + count01 + count10; + } + } + + result.pixel = uv.xy; + return result; +} + +@fragment +fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + + let pixel = vec2i(input.position.xy); + + #ifdef OUTPUT_LINEAR + // Linear index for output (simpler for consumers to read) + let index = u32(pixel.y) * u32(uniform.imageSize) + u32(pixel.x); + #else + // Morton index for internal passes (better cache locality) + let index = uvToIndex(pixel); + #endif + + if (index >= u32(uniform.elementCount)) { + output.color = vec4u(0xFFFFFFFFu, 0u, 0u, 1u); + output.color1 = vec4u(0xFFFFFFFFu, 0u, 0u, 1u); + return output; + } + + let prefixWidth = f32(uniform.imageSize * (1i << u32(uniform.bitsPerStep >> 1))) / f32(1i << u32(uniform.groupSize >> 1)); + let searchResult = activeTexelIndexToUV(prefixWidth, f32(index)); + + if (searchResult.pixel.x < 0) { + output.color = vec4u(0xFFFFFFFFu, 0u, 0u, 1u); + output.color1 = vec4u(0xFFFFFFFFu, 0u, 0u, 1u); + return output; + } + + let activeIndex = uvToIndex(searchResult.pixel); + let elementsLog2 = u32(uniform.imageElementsLog2); + let groupsLog2 = elementsLog2 - u32(uniform.groupSize); + let digitIndex = activeIndex >> groupsLog2; + let keyIndex = (activeIndex - (digitIndex << groupsLog2)) << u32(uniform.groupSize); + + // Linear search within the group - optimized with integer math and incremental coords + var outKey: u32 = 0u; + let mask = (1u << u32(uniform.bitsPerStep)) - 1u; + let localIndexU = u32(f32(index) - searchResult.prefixSum); + var localCountU: u32 = 0u; + var foundMortonIndex: u32 = keyIndex; + + #ifdef SOURCE_LINEAR + // Compute starting (x,y) once - only 1 div/mod instead of 16 + let sw = textureDimensions(keysTexture, 0).x; + let baseY = keyIndex / sw; + let baseX = keyIndex - baseY * sw; + var x = baseX; + var y = baseY; + + for (var i: u32 = 0u; i < 16u; i = i + 1u) { + let groupPixel = vec2i(i32(x), i32(y)); + outKey = textureLoad(keysTexture, groupPixel, 0).r; + + let digit = (outKey >> u32(uniform.currentBit)) & mask; + + if (digit == digitIndex) { + localCountU = localCountU + 1u; + if (localCountU > localIndexU) { + foundMortonIndex = keyIndex + i; + break; + } + } + + // Advance to next pixel with wrap + x = x + 1u; + if (x >= sw) { + x = 0u; + y = y + 1u; + } + } + #else + // Morton layout - can't use simple x++ increment, but still use integer math + for (var i: u32 = 0u; i < 16u; i = i + 1u) { + let mortonIndex = keyIndex + i; + let groupPixel = indexToUV(mortonIndex); + outKey = textureLoad(keysTexture, groupPixel, 0).r; + + let digit = (outKey >> u32(uniform.currentBit)) & mask; + + if (digit == digitIndex) { + localCountU = localCountU + 1u; + if (localCountU > localIndexU) { + foundMortonIndex = mortonIndex; + break; + } + } + } + #endif + + // Read indices after finding the match + #ifdef FIRST_PASS + // First pass: indices are implicitly [0,1,2,...], use index directly + let outIndex = foundMortonIndex; + #else + // Subsequent passes: read from shuffled indices texture + let indicesPixel = indexToUV(foundMortonIndex); + let outIndex = textureLoad(indicesTexture, indicesPixel, 0).r; + #endif + + output.color = vec4u(outKey, 0u, 0u, 1u); + output.color1 = vec4u(outIndex, 0u, 0u, 1u); + return output; +} +`; diff --git a/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js b/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js index 95174110c36..efe697aacf2 100644 --- a/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js +++ b/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js @@ -20,7 +20,7 @@ fn getAlbedo() { #endif #ifdef STD_DIFFUSE_VERTEX - dAlbedo = dAlbedo * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL})); + dAlbedo = dAlbedo * saturate3(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js b/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js index c6237aac4dc..461e8f8d663 100644 --- a/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js +++ b/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js @@ -10,7 +10,7 @@ fn getEmission() { #endif #ifdef STD_EMISSIVE_VERTEX - dEmission = dEmission * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL})); + dEmission = dEmission * saturate3(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/wgsl/collections/gsplat-chunks-wgsl.js b/src/scene/shader-lib/wgsl/collections/gsplat-chunks-wgsl.js index ce8625077d9..436140c10e5 100644 --- a/src/scene/shader-lib/wgsl/collections/gsplat-chunks-wgsl.js +++ b/src/scene/shader-lib/wgsl/collections/gsplat-chunks-wgsl.js @@ -6,6 +6,7 @@ import gsplatCompressedSHVS from '../chunks/gsplat/vert/gsplatCompressedSH.js'; import gsplatCustomizeVS from '../chunks/gsplat/vert/gsplatCustomize.js'; import gsplatEvalSHVS from '../chunks/gsplat/vert/gsplatEvalSH.js'; import gsplatHelpersVS from '../chunks/gsplat/vert/gsplatHelpers.js'; +import gsplatModifyVS from '../chunks/gsplat/vert/gsplatModify.js'; import gsplatQuatToMat3VS from '../chunks/gsplat/vert/gsplatQuatToMat3.js'; import gsplatSogsColorVS from '../chunks/gsplat/vert/gsplatSogsColor.js'; import gsplatSogsDataVS from '../chunks/gsplat/vert/gsplatSogsData.js'; @@ -32,6 +33,7 @@ export const gsplatChunksWGSL = { gsplatCustomizeVS, gsplatEvalSHVS, gsplatHelpersVS, + gsplatModifyVS, gsplatSourceFormatVS, gsplatStructsVS, gsplatQuatToMat3VS, diff --git a/src/scene/shader-pass.js b/src/scene/shader-pass.js index 93b29c86635..f4914387a49 100644 --- a/src/scene/shader-pass.js +++ b/src/scene/shader-pass.js @@ -1,7 +1,7 @@ import { Debug } from '../core/debug.js'; import { DeviceCache } from '../platform/graphics/device-cache.js'; import { - SHADER_FORWARD, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS + SHADER_FORWARD, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS, SHADER_DEPTH_PICK } from './constants.js'; /** @@ -25,7 +25,7 @@ class ShaderPassInfo { /** @type {string} */ name; - /** @type {Map} */ defines = new Map(); /** @@ -60,6 +60,10 @@ class ShaderPassInfo { keyword = 'FORWARD'; } else if (this.index === SHADER_PICK) { keyword = 'PICK'; + } else if (this.index === SHADER_DEPTH_PICK) { + // depth pick generates both PICK_PASS and DEPTH_PICK_PASS defines + keyword = 'PICK'; + this.defines.set('DEPTH_PICK_PASS', ''); } this.defines.set(`${keyword}_PASS`, ''); @@ -102,6 +106,7 @@ class ShaderPass { add('prepass', SHADER_PREPASS); add('shadow', SHADER_SHADOW); add('pick', SHADER_PICK); + add('depth_pick', SHADER_DEPTH_PICK); } /** diff --git a/test/core/set-utils.test.mjs b/test/core/set-utils.test.mjs new file mode 100644 index 00000000000..8a06b23e1d1 --- /dev/null +++ b/test/core/set-utils.test.mjs @@ -0,0 +1,76 @@ +import { expect } from 'chai'; + +import { SetUtils } from '../../src/core/set-utils.js'; + +describe('SetUtils', function () { + + describe('#equals', function () { + + it('returns true for equal sets with same elements', function () { + const setA = new Set([1, 2, 3]); + const setB = new Set([1, 2, 3]); + expect(SetUtils.equals(setA, setB)).to.be.true; + }); + + it('returns true for equal sets with different insertion order', function () { + const setA = new Set([3, 1, 2]); + const setB = new Set([1, 2, 3]); + expect(SetUtils.equals(setA, setB)).to.be.true; + }); + + it('returns false for sets with different elements', function () { + const setA = new Set([1, 2, 3]); + const setB = new Set([1, 2, 4]); + expect(SetUtils.equals(setA, setB)).to.be.false; + }); + + it('returns false for sets with different sizes', function () { + const setA = new Set([1, 2, 3]); + const setB = new Set([1, 2]); + expect(SetUtils.equals(setA, setB)).to.be.false; + }); + + it('returns true for two empty sets', function () { + const setA = new Set(); + const setB = new Set(); + expect(SetUtils.equals(setA, setB)).to.be.true; + }); + + it('returns true for sets with one element', function () { + const setA = new Set([42]); + const setB = new Set([42]); + expect(SetUtils.equals(setA, setB)).to.be.true; + }); + + it('returns false for sets with different single elements', function () { + const setA = new Set([42]); + const setB = new Set([43]); + expect(SetUtils.equals(setA, setB)).to.be.false; + }); + + it('returns true for sets with multiple elements', function () { + const setA = new Set(['a', 'b', 'c', 'd', 'e']); + const setB = new Set(['e', 'd', 'c', 'b', 'a']); + expect(SetUtils.equals(setA, setB)).to.be.true; + }); + + it('returns true for sets with object references', function () { + const obj1 = { id: 1 }; + const obj2 = { id: 2 }; + const setA = new Set([obj1, obj2]); + const setB = new Set([obj2, obj1]); + expect(SetUtils.equals(setA, setB)).to.be.true; + }); + + it('returns false for sets with different object references', function () { + const obj1 = { id: 1 }; + const obj2 = { id: 2 }; + const obj3 = { id: 3 }; + const setA = new Set([obj1, obj2]); + const setB = new Set([obj2, obj3]); + expect(SetUtils.equals(setA, setB)).to.be.false; + }); + + }); + +}); diff --git a/test/core/shape/bounding-box.test.mjs b/test/core/shape/bounding-box.test.mjs new file mode 100644 index 00000000000..c0148261774 --- /dev/null +++ b/test/core/shape/bounding-box.test.mjs @@ -0,0 +1,301 @@ +import { expect } from 'chai'; + +import { Vec3 } from '../../../src/core/math/vec3.js'; +import { BoundingBox } from '../../../src/core/shape/bounding-box.js'; + +describe('BoundingBox', function () { + + describe('#containsPoint', function () { + + it('returns true for point at center', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, 0); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point inside box', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(2, 2, 2)); + const point = new Vec3(0.5, 0.5, 0.5); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point at min corner', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(-1, -1, -1); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point at max corner', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(1, 1, 1); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point on min X face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(-1, 0, 0); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point on max X face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(1, 0, 0); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point on min Y face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, -1, 0); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point on max Y face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 1, 0); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point on min Z face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, -1); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns true for point on max Z face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, 1); + expect(box.containsPoint(point)).to.equal(true); + }); + + it('returns false for point outside on negative X', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(-1.1, 0, 0); + expect(box.containsPoint(point)).to.equal(false); + }); + + it('returns false for point outside on positive X', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(1.1, 0, 0); + expect(box.containsPoint(point)).to.equal(false); + }); + + it('returns false for point outside on negative Y', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, -1.1, 0); + expect(box.containsPoint(point)).to.equal(false); + }); + + it('returns false for point outside on positive Y', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 1.1, 0); + expect(box.containsPoint(point)).to.equal(false); + }); + + it('returns false for point outside on negative Z', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, -1.1); + expect(box.containsPoint(point)).to.equal(false); + }); + + it('returns false for point outside on positive Z', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, 1.1); + expect(box.containsPoint(point)).to.equal(false); + }); + + it('works with non-centered box', function () { + const box = new BoundingBox(new Vec3(5, 10, 15), new Vec3(2, 3, 4)); + expect(box.containsPoint(new Vec3(5, 10, 15))).to.equal(true); // center + expect(box.containsPoint(new Vec3(3, 7, 11))).to.equal(true); // min corner + expect(box.containsPoint(new Vec3(7, 13, 19))).to.equal(true); // max corner + expect(box.containsPoint(new Vec3(2.9, 10, 15))).to.equal(false); // outside + expect(box.containsPoint(new Vec3(7.1, 10, 15))).to.equal(false); // outside + }); + + it('works with asymmetric box', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 5, 10)); + expect(box.containsPoint(new Vec3(0, 0, 0))).to.equal(true); + expect(box.containsPoint(new Vec3(1, 5, 10))).to.equal(true); + expect(box.containsPoint(new Vec3(-1, -5, -10))).to.equal(true); + expect(box.containsPoint(new Vec3(1.1, 0, 0))).to.equal(false); + expect(box.containsPoint(new Vec3(0, 5.1, 0))).to.equal(false); + expect(box.containsPoint(new Vec3(0, 0, 10.1))).to.equal(false); + }); + + }); + + describe('#closestPoint', function () { + + it('returns the point itself when inside the box', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0.5, 0.5, 0.5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(0.5); + expect(closest.y).to.equal(0.5); + expect(closest.z).to.equal(0.5); + }); + + it('returns the point itself when at center', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(0); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(0); + }); + + it('returns point on positive X face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(5, 0, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(0); + }); + + it('returns point on negative X face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(-5, 0, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(-1); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(0); + }); + + it('returns point on positive Y face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 5, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(0); + expect(closest.y).to.equal(1); + expect(closest.z).to.equal(0); + }); + + it('returns point on negative Y face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, -5, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(0); + expect(closest.y).to.equal(-1); + expect(closest.z).to.equal(0); + }); + + it('returns point on positive Z face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, 5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(0); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(1); + }); + + it('returns point on negative Z face', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(0, 0, -5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(0); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(-1); + }); + + it('returns corner point when outside diagonally', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(5, 5, 5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(1); + expect(closest.z).to.equal(1); + }); + + it('returns opposite corner when outside negative diagonal', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(-5, -5, -5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(-1); + expect(closest.y).to.equal(-1); + expect(closest.z).to.equal(-1); + }); + + it('clamps to edge when outside on one axis', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(2, 0.5, 0.5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(0.5); + expect(closest.z).to.equal(0.5); + }); + + it('clamps to edge when outside on two axes', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(2, 2, 0.5); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(1); + expect(closest.z).to.equal(0.5); + }); + + it('works with non-centered box', function () { + const box = new BoundingBox(new Vec3(5, 10, 15), new Vec3(2, 3, 4)); + const point = new Vec3(10, 10, 15); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(7); // max X is 5+2=7 + expect(closest.y).to.equal(10); + expect(closest.z).to.equal(15); + }); + + it('works with asymmetric box', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 5, 10)); + const point = new Vec3(2, 7, -15); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(5); + expect(closest.z).to.equal(-10); + }); + + it('returns point on boundary when exactly on boundary', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(1, 0, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(0); + }); + + it('clamps point just slightly outside', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(1.1, 0, 0); + const closest = box.closestPoint(point); + expect(closest.x).to.equal(1); + expect(closest.y).to.equal(0); + expect(closest.z).to.equal(0); + }); + + it('uses provided result vector', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const point = new Vec3(2, 0, 0); + const result = new Vec3(); + const returned = box.closestPoint(point, result); + expect(returned).to.equal(result); // Should return the same object + expect(result.x).to.equal(1); + expect(result.y).to.equal(0); + expect(result.z).to.equal(0); + }); + + it('reuses result vector on multiple calls', function () { + const box = new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 1, 1)); + const result = new Vec3(); + + box.closestPoint(new Vec3(2, 0, 0), result); + expect(result.x).to.equal(1); + expect(result.y).to.equal(0); + expect(result.z).to.equal(0); + + box.closestPoint(new Vec3(0, 2, 0), result); + expect(result.x).to.equal(0); + expect(result.y).to.equal(1); + expect(result.z).to.equal(0); + }); + + }); + +}); diff --git a/test/framework/components/element/draw-order.test.mjs b/test/framework/components/element/draw-order.test.mjs index cdebc0d63ed..7a92e609155 100644 --- a/test/framework/components/element/draw-order.test.mjs +++ b/test/framework/components/element/draw-order.test.mjs @@ -299,4 +299,94 @@ describe('ElementComponent Draw Order', function () { expect(beforeResult.m3Unmask).to.equal(afterResult.m3Unmask); }); + it('screen priority in valid range (0-127)', function () { + const screen = new Entity('screen'); + screen.addComponent('screen'); + + // Test valid range boundaries + screen.screen.priority = 0; + expect(screen.screen.priority).to.equal(0); + + screen.screen.priority = 127; + expect(screen.screen.priority).to.equal(127); + + screen.screen.priority = 64; + expect(screen.screen.priority).to.equal(64); + }); + + it('screen priority clamping (values > 127)', function () { + const screen = new Entity('screen'); + screen.addComponent('screen'); + + // Test that values > 127 are clamped + screen.screen.priority = 128; + expect(screen.screen.priority).to.equal(127); + + screen.screen.priority = 255; + expect(screen.screen.priority).to.equal(127); + + screen.screen.priority = 1000; + expect(screen.screen.priority).to.equal(127); + }); + + it('screen priority clamping (negative values)', function () { + const screen = new Entity('screen'); + screen.addComponent('screen'); + + // Test that negative values are clamped to 0 + screen.screen.priority = -1; + expect(screen.screen.priority).to.equal(0); + + screen.screen.priority = -100; + expect(screen.screen.priority).to.equal(0); + }); + + it('multiple screens with different priorities sort correctly', function () { + const screen1 = new Entity('screen1'); + screen1.addComponent('screen', { priority: 0 }); + + const screen2 = new Entity('screen2'); + screen2.addComponent('screen', { priority: 63 }); + + const screen3 = new Entity('screen3'); + screen3.addComponent('screen', { priority: 127 }); + + const elem1 = new Entity('elem1'); + elem1.addComponent('element'); + + const elem2 = new Entity('elem2'); + elem2.addComponent('element'); + + const elem3 = new Entity('elem3'); + elem3.addComponent('element'); + + screen1.addChild(elem1); + screen2.addChild(elem2); + screen3.addChild(elem3); + + app.root.addChild(screen1); + app.root.addChild(screen2); + app.root.addChild(screen3); + + // update forces draw order sync + app.tick(); + + // Elements should have drawOrder with priority in top 8 bits + // Priority 0: 0x00000001 + // Priority 63: 0x3F000001 + // Priority 127: 0x7F000001 + expect(elem1.element.drawOrder).to.equal(0x00000001); + expect(elem2.element.drawOrder).to.equal(0x3F000001); + expect(elem3.element.drawOrder).to.equal(0x7F000001); + + // Verify sorting: higher priority should have higher drawOrder + expect(elem3.element.drawOrder).to.be.greaterThan(elem2.element.drawOrder); + expect(elem2.element.drawOrder).to.be.greaterThan(elem1.element.drawOrder); + + // Verify all drawOrder values are positive (no sign bit overflow) + expect(elem1.element.drawOrder).to.be.greaterThan(0); + expect(elem2.element.drawOrder).to.be.greaterThan(0); + expect(elem3.element.drawOrder).to.be.greaterThan(0); + }); + }); diff --git a/test/framework/components/script/component.test.mjs b/test/framework/components/script/component.test.mjs index a2b69a3e70a..b455abe21f1 100644 --- a/test/framework/components/script/component.test.mjs +++ b/test/framework/components/script/component.test.mjs @@ -1668,6 +1668,8 @@ describe('ScriptComponent', function () { app.start(); + app.update(); + expect(window.initializeCalls.length).to.equal(8); let idx = 0; checkInitCall(e, idx++, 'initialize destroyer'); diff --git a/test/platform/net/http.test.mjs b/test/platform/net/http.test.mjs index b3228f820b9..7da51cb7002 100644 --- a/test/platform/net/http.test.mjs +++ b/test/platform/net/http.test.mjs @@ -1,5 +1,6 @@ import { expect } from 'chai'; -import { restore, spy, useFakeXMLHttpRequest } from 'sinon'; +import nise from 'nise'; +import { restore, spy } from 'sinon'; import { http, Http } from '../../../src/platform/net/http.js'; @@ -65,7 +66,7 @@ describe('Http', function () { spy(http, 'request'); let requests = 0; - const xhr = useFakeXMLHttpRequest(); + const xhr = nise.fakeXhr.useFakeXMLHttpRequest(); // Store original XMLHttpRequest const originalXHR = global.XMLHttpRequest; @@ -103,7 +104,7 @@ describe('Http', function () { }); it('status 0 returns "Network error"', function (done) { - const xhr = useFakeXMLHttpRequest(); + const xhr = nise.fakeXhr.useFakeXMLHttpRequest(); let isDone = false; // Store original XMLHttpRequest diff --git a/test/scene/materials/standard-material.test.mjs b/test/scene/materials/standard-material.test.mjs index 4f8ae5670be..9e64209f974 100644 --- a/test/scene/materials/standard-material.test.mjs +++ b/test/scene/materials/standard-material.test.mjs @@ -291,6 +291,7 @@ describe('StandardMaterial', function () { expect(material.useMetalness).to.equal(false); expect(material.useMetalnessSpecularColor).to.equal(false); expect(material.useSkybox).to.equal(true); + expect(material.vertexColorGamma).to.equal(false); } describe('#constructor()', function () {