diff --git a/.gitignore b/.gitignore index 1698cee3..03363a73 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,11 @@ desktop.ini .zed .idea .project + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ +/tests/testAssetDownloads diff --git a/.npmignore b/.npmignore index c7e90d7e..a510f369 100644 --- a/.npmignore +++ b/.npmignore @@ -12,6 +12,8 @@ documentation/ .gitattributes .gitmodules .eslintrc.json +dist/main.js +dist/index.html # files types to ignore rollup.config.js diff --git a/.prettierignore b/.prettierignore index 26a3c207..4a599ffe 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -source/libs \ No newline at end of file +source/libs +**/*.md diff --git a/API.md b/API.md index cbd2bf65..f0d56285 100644 --- a/API.md +++ b/API.md @@ -7,12 +7,18 @@
GltfState containing a state for visualization in GltfView
AnimationTimer class to control animation playback.
+ResourceLoader can be used to load resources for the GltfState that are then used to display the loaded data with GltfView
A controller for managing KHR_interactivity graphs in a glTF scene.
+GltfState](#GltfState)
+
+
+### gltfState.graphController
+the graph controller allows selecting and playing graphs from KHR_interactivity
+
+**Kind**: instance property of [GltfState](#GltfState)
+
+
+### gltfState.selectionCallback
+callback for selection: (selectionInfo : {
+node,
+position,
+rayOrigin,
+controller }) => {}
+
+**Kind**: instance property of [GltfState](#GltfState)
+
+
+### gltfState.hoverCallback
+callback for hovering: (hoverInfo : { node, controller }) => {}
+
+**Kind**: instance property of [GltfState](#GltfState)
+
+
+### gltfState.triggerSelection
+If the renderer should compute selection information in the next frame. Is automatically reset after the frame is rendered
+
+**Kind**: instance property of [GltfState](#GltfState)
+
+
+### gltfState.enableHover
+If the renderer should compute hover information in the next frame.
+
**Kind**: instance property of [GltfState](#GltfState)
@@ -244,6 +307,23 @@ parameters used to configure the rendering
* [.renderingParameters](#GltfState+renderingParameters)
* [.morphing](#GltfState+renderingParameters.morphing)
* [.skinning](#GltfState+renderingParameters.skinning)
+ * [.enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+ * [.KHR_materials_clearcoat](#GltfState+renderingParameters.enabledExtensions.KHR_materials_clearcoat)
+ * [.KHR_materials_sheen](#GltfState+renderingParameters.enabledExtensions.KHR_materials_sheen)
+ * [.KHR_materials_transmission](#GltfState+renderingParameters.enabledExtensions.KHR_materials_transmission)
+ * [.KHR_materials_volume](#GltfState+renderingParameters.enabledExtensions.KHR_materials_volume)
+ * [.KHR_materials_volume_scatter](#GltfState+renderingParameters.enabledExtensions.KHR_materials_volume_scatter)
+ * [.KHR_materials_ior](#GltfState+renderingParameters.enabledExtensions.KHR_materials_ior)
+ * [.KHR_materials_specular](#GltfState+renderingParameters.enabledExtensions.KHR_materials_specular)
+ * [.KHR_materials_iridescence](#GltfState+renderingParameters.enabledExtensions.KHR_materials_iridescence)
+ * [.KHR_materials_diffuse_transmission](#GltfState+renderingParameters.enabledExtensions.KHR_materials_diffuse_transmission)
+ * [.KHR_materials_anisotropy](#GltfState+renderingParameters.enabledExtensions.KHR_materials_anisotropy)
+ * [.KHR_materials_dispersion](#GltfState+renderingParameters.enabledExtensions.KHR_materials_dispersion)
+ * [.KHR_materials_emissive_strength](#GltfState+renderingParameters.enabledExtensions.KHR_materials_emissive_strength)
+ * [.KHR_interactivity](#GltfState+renderingParameters.enabledExtensions.KHR_interactivity)
+ * [.KHR_node_hoverability](#GltfState+renderingParameters.enabledExtensions.KHR_node_hoverability)
+ * [.KHR_node_selectability](#GltfState+renderingParameters.enabledExtensions.KHR_node_selectability)
+ * [.KHR_node_visibility](#GltfState+renderingParameters.enabledExtensions.KHR_node_visibility)
* [.clearColor](#GltfState+renderingParameters.clearColor)
* [.exposure](#GltfState+renderingParameters.exposure)
* [.usePunctual](#GltfState+renderingParameters.usePunctual)
@@ -269,6 +349,127 @@ morphing between vertices
skin / skeleton
**Kind**: static property of [renderingParameters](#GltfState+renderingParameters)
+
+
+#### renderingParameters.enabledExtensions
+enabled extensions
+
+**Kind**: static property of [renderingParameters](#GltfState+renderingParameters)
+
+* [.enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+ * [.KHR_materials_clearcoat](#GltfState+renderingParameters.enabledExtensions.KHR_materials_clearcoat)
+ * [.KHR_materials_sheen](#GltfState+renderingParameters.enabledExtensions.KHR_materials_sheen)
+ * [.KHR_materials_transmission](#GltfState+renderingParameters.enabledExtensions.KHR_materials_transmission)
+ * [.KHR_materials_volume](#GltfState+renderingParameters.enabledExtensions.KHR_materials_volume)
+ * [.KHR_materials_volume_scatter](#GltfState+renderingParameters.enabledExtensions.KHR_materials_volume_scatter)
+ * [.KHR_materials_ior](#GltfState+renderingParameters.enabledExtensions.KHR_materials_ior)
+ * [.KHR_materials_specular](#GltfState+renderingParameters.enabledExtensions.KHR_materials_specular)
+ * [.KHR_materials_iridescence](#GltfState+renderingParameters.enabledExtensions.KHR_materials_iridescence)
+ * [.KHR_materials_diffuse_transmission](#GltfState+renderingParameters.enabledExtensions.KHR_materials_diffuse_transmission)
+ * [.KHR_materials_anisotropy](#GltfState+renderingParameters.enabledExtensions.KHR_materials_anisotropy)
+ * [.KHR_materials_dispersion](#GltfState+renderingParameters.enabledExtensions.KHR_materials_dispersion)
+ * [.KHR_materials_emissive_strength](#GltfState+renderingParameters.enabledExtensions.KHR_materials_emissive_strength)
+ * [.KHR_interactivity](#GltfState+renderingParameters.enabledExtensions.KHR_interactivity)
+ * [.KHR_node_hoverability](#GltfState+renderingParameters.enabledExtensions.KHR_node_hoverability)
+ * [.KHR_node_selectability](#GltfState+renderingParameters.enabledExtensions.KHR_node_selectability)
+ * [.KHR_node_visibility](#GltfState+renderingParameters.enabledExtensions.KHR_node_visibility)
+
+
+
+##### enabledExtensions.KHR\_materials\_clearcoat
+KHR_materials_clearcoat adds a clear coat layer on top of the glTF base material
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_sheen
+KHR_materials_sheen adds a sheen layer on top of the glTF base material
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_transmission
+KHR_materials_transmission adds physical-based transparency
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_volume
+KHR_materials_volume adds support for volumetric materials. Used together with KHR_materials_transmission and KHR_materials_diffuse_transmission
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_volume\_scatter
+KHR_materials_volume_scatter allows the simulation of scattering light inside a volume. Used together with KHR_materials_volume
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_ior
+KHR_materials_ior makes the index of refraction configurable
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_specular
+KHR_materials_specular allows configuring specular color (f0 color) and amount of specular reflection
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_iridescence
+KHR_materials_iridescence adds a thin-film iridescence effect
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_diffuse\_transmission
+KHR_materials_diffuse_transmission allows light to pass diffusely through the material
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_anisotropy
+KHR_materials_anisotropy defines microfacet grooves in the surface, stretching the specular reflection on the surface
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_dispersion
+KHR_materials_dispersion defines configuring the strength of the angular separation of colors (chromatic abberation)
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_materials\_emissive\_strength
+KHR_materials_emissive_strength enables emissive factors larger than 1.0
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_interactivity
+KHR_interactivity enables execution of a behavior graph
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_node\_hoverability
+KHR_node_hoverability enables hovering over nodes
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_node\_selectability
+KHR_node_selectability enables selecting nodes
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
+
+
+##### enabledExtensions.KHR\_node\_visibility
+KHR_node_visibility enables controlling the visibility of nodes
+
+**Kind**: static property of [enabledExtensions](#GltfState+renderingParameters.enabledExtensions)
#### renderingParameters.clearColor
@@ -408,6 +609,7 @@ such as "NORMAL"
* [.GEOMETRYNORMAL](#GltfState.DebugOutput.generic.GEOMETRYNORMAL)
* [.TANGENT](#GltfState.DebugOutput.generic.TANGENT)
* [.BITANGENT](#GltfState.DebugOutput.generic.BITANGENT)
+ * [.TANGENTW](#GltfState.DebugOutput.generic.TANGENTW)
* [.WORLDSPACENORMAL](#GltfState.DebugOutput.generic.WORLDSPACENORMAL)
* [.ALPHA](#GltfState.DebugOutput.generic.ALPHA)
* [.OCCLUSION](#GltfState.DebugOutput.generic.OCCLUSION)
@@ -459,6 +661,7 @@ generic debug outputs
* [.GEOMETRYNORMAL](#GltfState.DebugOutput.generic.GEOMETRYNORMAL)
* [.TANGENT](#GltfState.DebugOutput.generic.TANGENT)
* [.BITANGENT](#GltfState.DebugOutput.generic.BITANGENT)
+ * [.TANGENTW](#GltfState.DebugOutput.generic.TANGENTW)
* [.WORLDSPACENORMAL](#GltfState.DebugOutput.generic.WORLDSPACENORMAL)
* [.ALPHA](#GltfState.DebugOutput.generic.ALPHA)
* [.OCCLUSION](#GltfState.DebugOutput.generic.OCCLUSION)
@@ -499,6 +702,12 @@ output the tangent from the TBN
##### generic.BITANGENT
output the bitangent from the TBN
+**Kind**: static property of [generic](#GltfState.DebugOutput.generic)
+
+
+##### generic.TANGENTW
+output the tangent w from the TBN (black corresponds to -1; white to 1
+
**Kind**: static property of [generic](#GltfState.DebugOutput.generic)
@@ -722,6 +931,69 @@ output the anisotropic strength
output final direction as defined by the anisotropyTexture and rotation
**Kind**: static property of [anisotropy](#GltfState.DebugOutput.anisotropy)
+
+
+## AnimationTimer
+AnimationTimer class to control animation playback.
+
+**Kind**: global class
+
+* [AnimationTimer](#AnimationTimer)
+ * [.start()](#AnimationTimer+start)
+ * [.pause()](#AnimationTimer+pause)
+ * [.unpause()](#AnimationTimer+unpause)
+ * [.toggle()](#AnimationTimer+toggle)
+ * [.reset()](#AnimationTimer+reset)
+ * [.setFixedTime(timeInSec)](#AnimationTimer+setFixedTime)
+ * [.elapsedSec()](#AnimationTimer+elapsedSec)
+
+
+
+### animationTimer.start()
+Start the animation timer and all animations
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
+
+
+### animationTimer.pause()
+Pause all animations
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
+
+
+### animationTimer.unpause()
+Unpause all animations
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
+
+
+### animationTimer.toggle()
+Toggle the animation playback state
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
+
+
+### animationTimer.reset()
+Reset the animation timer. If animations were playing, they will be restarted.
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
+
+
+### animationTimer.setFixedTime(timeInSec)
+Plays all animations starting from the specified time
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| timeInSec | number | The time in seconds to set the animation timer to |
+
+
+
+### animationTimer.elapsedSec()
+Get the elapsed time in seconds
+
+**Kind**: instance method of [AnimationTimer](#AnimationTimer)
## ResourceLoader
@@ -992,3 +1264,100 @@ Fit view to updated canvas size without changing rotation if distance is incorre
| gltf | Gltf |
| sceneIndex | number |
+
+
+## GraphController
+A controller for managing KHR_interactivity graphs in a glTF scene.
+
+**Kind**: global class
+
+* [GraphController](#GraphController)
+ * [.initializeGraphs(state)](#GraphController+initializeGraphs)
+ * [.loadGraph(graphIndex)](#GraphController+loadGraph) ⇒ Array
+ * [.stopGraphEngine()](#GraphController+stopGraphEngine)
+ * [.pauseGraph()](#GraphController+pauseGraph)
+ * [.resumeGraph()](#GraphController+resumeGraph)
+ * [.resetGraph()](#GraphController+resetGraph)
+ * [.dispatchEvent(eventName, data)](#GraphController+dispatchEvent)
+ * [.addCustomEventListener(eventName, callback)](#GraphController+addCustomEventListener)
+ * [.clearCustomEventListeners()](#GraphController+clearCustomEventListeners)
+
+
+
+### graphController.initializeGraphs(state)
+Initialize the graph controller with the given state.
+This needs to be called every time a glTF assets is loaded.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| state | [GltfState](#GltfState) | The state of the application. |
+
+
+
+### graphController.loadGraph(graphIndex) ⇒ Array
+Loads the specified graph. Resets the engine. Starts playing if this.playing is true.
+
+**Kind**: instance method of [GraphController](#GraphController)
+**Returns**: Array - An array of custom events defined in the graph.
+
+| Param | Type |
+| --- | --- |
+| graphIndex | number |
+
+
+
+### graphController.stopGraphEngine()
+Stops the graph engine.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+
+### graphController.pauseGraph()
+Pauses the currently playing graph.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+
+### graphController.resumeGraph()
+Resumes the currently paused graph.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+
+### graphController.resetGraph()
+Resets the current graph.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+
+### graphController.dispatchEvent(eventName, data)
+Dispatches an event to the behavior engine.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+| Param | Type |
+| --- | --- |
+| eventName | string |
+| data | \* |
+
+
+
+### graphController.addCustomEventListener(eventName, callback)
+Adds a custom event listener to the decorator.
+Khronos test assets use test/onStart, test/onFail and test/onSuccess.
+
+**Kind**: instance method of [GraphController](#GraphController)
+
+| Param | Type |
+| --- | --- |
+| eventName | string |
+| callback | function |
+
+
+
+### graphController.clearCustomEventListeners()
+Clears all custom event listeners from the decorator.
+
+**Kind**: instance method of [GraphController](#GraphController)
diff --git a/README.md b/README.md
index 6de47b75..c6c6c457 100644
--- a/README.md
+++ b/README.md
@@ -9,27 +9,38 @@ Try out the [glTF Sample Viewer](https://github.khronos.org/glTF-Sample-Viewer-R
## Table of Contents
- [Khronos glTF Sample Renderer](#khronos-gltf-sample-renderer)
- - [Table of Contents](#table-of-contents)
- - [Credits](#credits)
- - [Features](#features)
- - [API](#api)
- - [GltfView](#gltfview)
- - [GltfState](#gltfstate)
- - [ResourceLoader](#resourceloader)
- - [Render Fidelity Tools](#render-fidelity-tools)
+ - [Table of Contents](#table-of-contents)
+ - [Credits](#credits)
+ - [Features](#features)
+ - [API](#api)
+ - [GltfView](#gltfview)
+ - [GltfState](#gltfstate)
+ - [GraphController](#graphcontroller)
+ - [AnimationTimer](#animationtimer)
+ - [ResourceLoader](#resourceloader)
+ - [Render Fidelity Tools](#render-fidelity-tools)
+ - [Development](#development)
- [Formatting](#formatting)
- - [Visual Studio Code](#visual-studio-code)
+ - [Visual Studio Code](#visual-studio-code)
+ - [Testing](#testing)
## Credits
Developed and refactored by [UX3D](https://www.ux3d.io/). Supported by the [Khronos Group](https://www.khronos.org/) and by [Google](https://www.google.com/) for the glTF Draco mesh compression import.
Formerly hosted together with the example frontend at the [glTF Sample Viewer](https://github.com/KhronosGroup/glTF-Sample-Viewer) repository. Original code based on the concluded [glTF-WebGL-PBR](https://github.com/KhronosGroup/glTF-Sample-Viewer/tree/glTF-WebGL-PBR) project. Previously supported by [Facebook](https://www.facebook.com/) for animations, skinning and morphing.
+For KHR_interactivity, the behavior engine of the [glTF-InteractivityGraph-AuthoringTool](https://github.com/KhronosGroup/glTF-InteractivityGraph-AuthoringTool) is used.
## Features
- [x] glTF 2.0
+- [KHR_accessor_float64](https://github.com/KhronosGroup/glTF/pull/2397)
+ - [x] Animations
+ - [x] KHR_animation_pointer
+ - [ ] Mesh Attributes not supported since WebGL2 only supports 32 bit
+ - [ ] Skins not supported since WebGL2 only supports 32 bit
- [x] [KHR_animation_pointer](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer)
- [x] [KHR_draco_mesh_compression](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_draco_mesh_compression)
+- [x] [KHR_interactivity](https://github.com/KhronosGroup/glTF/pull/2293)
- [x] [KHR_lights_punctual](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual)
- [x] [KHR_materials_anisotropy](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy)
- [x] [KHR_materials_clearcoat](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_clearcoat)
@@ -49,6 +60,9 @@ Formerly hosted together with the example frontend at the [glTF Sample Viewer](h
- [x] For dense volumes using KHR_materials_diffuse_transmission
- [ ] For sparse volumes using KHR_materials_transmission
- [x] [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_mesh_quantization)
+- [x] [KHR_node_hoverability](https://github.com/KhronosGroup/glTF/pull/2426)
+- [x] [KHR_node_selectability](https://github.com/KhronosGroup/glTF/pull/2422)
+- [x] [KHR_node_visibility](https://github.com/KhronosGroup/glTF/pull/2410)
- [x] [KHR_texture_basisu](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_basisu)
- [x] [KHR_texture_transform](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform)
- [x] [KHR_xmp_json_ld](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_xmp_json_ld)
@@ -94,6 +108,21 @@ state.animationTimer.start();
The state is passed to the `view.renderFrame` function to specify the content that should be rendered.
+#### GraphController
+
+The GltfState contains an instance of the GraphController which can be used to load and execute `KHR_interactivity` graphs. One can also send custom events to the graph or subscribe to custom event via callbacks.
+
+In the GltfState you can define an array of selection and hover points. Each element of the array represents one controller. If `triggerSelection` is set to `true`, the renderer will return the picking result of the clicked position via `selectionCallback`. The interactivity engine will be notified as well, if `KHR_node_selectability` is used in the current glTF.
+
+If `enableHover` is set to `true`, the renderer will return the picking result of the hovered position via `hoverCallback`. The interactivity engine receives hover results independent of `enableHover` based on the `hoverPositions` array. `enableHover` enables the use of custom hover handling independent of `KHR_interactivity` and is set to `false` by default.
+
+To make sure that `KHR_interactivity` always behaves correctly together with `KHR_node_selectability` and `KHR_node_hoverability`, update the values in the `hoverPositions` and `selectionPositions` arrays and trigger selections via `triggerSelection`. Currently, only one controller is supported. All entries except the first one of each array are ignored. Arrays are used to enable multiple controllers in the future without breaking the API.
+
+#### AnimationTimer
+
+The GltfState contains an instance of the AnimationTimer, which is used to play, pause and reset animations. It needs to be started to enable animations.
+The `KHR_interactivity` extension controls animations if present. Therefore, the GraphController uses the time of the AnimationTimer to control animations. The GraphController is paused and resumed independently from the AnimationTimer, thus if an interactivity graph is paused, currently playing animations will continue playing if the AnimationTimer is not paused as well.
+
### ResourceLoader
The ResourceLoader can be used to load external resources and make them available to the renderer.
@@ -106,7 +135,21 @@ state.gltf = await resourceLoader.loadGltf("path/to/some.gltf");
The glTF Sample Renderer is integrated into Google's [render fidelity tools](https://github.com/google/model-viewer/tree/master/packages/render-fidelity-tools). The render fidelity tools allow the comparison of different renderers. To run the project follow the instructions [here](https://github.com/google/model-viewer/blob/master/README.md) and [here](https://github.com/google/model-viewer/blob/master/packages/render-fidelity-tools/README.md). For information on how the glTF Sample Renderer was integrated see the [pull request on Github](https://github.com/google/model-viewer/pull/1962).
-## Formatting
+## Development
+
+After cloning this repository, run
+
+```
+npm install
+```
+
+to install all dependencies. To test and view your changes on a canvas, it is recommended to clone [glTF Sample Viewer](https://github.com/KhronosGroup/glTF-Sample-Viewer) which uses this renderer as a submodule.
+
+`npm run build` will build the npm package and put the bundled code into the `dist` directory.
+
+`npm run build_docs` will regenerate the [API documentation](API.md).
+
+### Formatting
This repository uses [Prettier](https://prettier.io/) for code formatting and [ESLint](https://eslint.org/) for linting.
@@ -126,3 +169,27 @@ There are extensions for both Prettier and ESLint in Visual Studio Code. They ca
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
You are encouraged to run Prettier and ESLint on your code before committing.
+
+### Testing
+
+glTF-Sample-Render uses [Playwright](https://playwright.dev/) for testing.\
+Currently, only `KHR_interactivity` tests are implemented.
+
+To run the tests run
+
+```
+npm run test
+```
+
+Playwright creates a new browser instance for each test. It can run on Chrome, Safari, Firefox and emulated mobile browsers. After all tests were run, a browser window with a summary will open. The `tests/testApp` directory contains a minimal frontend to be able to start a testing server. The server is started automatically. For debugging the test server you can also start it manually by running
+
+```
+npm run testApp
+```
+
+Tests are defined in the `tests` directory by files with the `.spec.ts` ending.\
+The interactivity tests download all test assets from the [glTF-Test-Assets-Interactivity repository](https://github.com/KhronosGroup/glTF-Test-Assets-Interactivity), loads each test file and listens on the `test/onStart`, `test/onSuccess` and `test/onFailed` events to determine if an interactivity test passes or not. `test/onStart` returns the needed execution time in seconds.
+
+You can also run more complex Playwright commands such as `npx playwright test --ui` or `npx playwright test --project chromium`. For more information check https://playwright.dev/docs/running-tests
+
+One can also use the [Playwright extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) to run the test more easily with advanced parameters, run tests only selectively or debug tests by adding breakpoints.
diff --git a/eslint.config.js b/eslint.config.js
index 95d627ae..8209406d 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -23,7 +23,12 @@ export default [
semi: "warn",
"no-extra-semi": "warn",
"no-undef": "warn",
- "no-unused-vars": "warn",
+ "no-unused-vars": [
+ "warn",
+ {
+ argsIgnorePattern: "^_"
+ }
+ ],
"no-empty": "warn",
"no-redeclare": "warn",
"no-prototype-builtins": "warn",
diff --git a/package-lock.json b/package-lock.json
index a361f1a7..f3c77c40 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.1.0",
"license": "Apache-2.0",
"dependencies": {
+ "@khronosgroup/gltf-interactivity-sample-engine": "file:../../glTF-InteractivityGraph-AuthoringTool/src/BasicBehaveEngine",
"fast-png": "^6.2.0",
"gl-matrix": "^3.2.1",
"globals": "^15.5.0",
@@ -16,46 +17,60 @@
"json-ptr": "^3.1.0"
},
"devDependencies": {
+ "@playwright/test": "^1.56.0",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-wasm": "^6.2.2",
+ "@types/node": "^24.7.2",
"concurrently": "^8.2.2",
"eslint": "^9.5.0",
"eslint-config-prettier": "^10.1.8",
- "jsdoc-to-markdown": "^8.0.1",
+ "jsdoc-to-markdown": "^9.1.3",
"prettier": "3.6.2",
"rollup": "^4.23.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-glslify": "^1.3.1",
"rollup-plugin-license": "^3.5.2",
- "serve": "^14.2.4"
+ "serve": "^14.2.5"
+ }
+ },
+ "../../glTF-InteractivityGraph-AuthoringTool/src/BasicBehaveEngine": {
+ "name": "@khronosgroup/gltf-interactivity-sample-engine",
+ "version": "0.1.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gl-matrix": "^3.4.3"
+ },
+ "devDependencies": {
+ "@types/node": "^24.1.0",
+ "typescript": "^5.8.3"
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
- "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
- "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "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,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
- "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.25.6"
+ "@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -74,14 +89,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
- "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"dependencies": {
- "@babel/helper-string-parser": "^7.24.8",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -100,16 +114,19 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
- "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "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,
"dependencies": {
- "eslint-visitor-keys": "^3.3.0"
+ "eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
@@ -313,9 +330,9 @@
"dev": true
},
"node_modules/@jsdoc/salty": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz",
- "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==",
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz",
+ "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==",
"dev": true,
"dependencies": {
"lodash": "^4.17.21"
@@ -324,6 +341,10 @@
"node": ">=v12.0.0"
}
},
+ "node_modules/@khronosgroup/gltf-interactivity-sample-engine": {
+ "resolved": "../../glTF-InteractivityGraph-AuthoringTool/src/BasicBehaveEngine",
+ "link": true
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -369,6 +390,21 @@
"node": ">=14"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz",
+ "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.56.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@rollup/plugin-commonjs": {
"version": "26.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.3.tgz",
@@ -728,12 +764,12 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "22.7.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
- "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
+ "version": "24.7.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
+ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
"dev": true,
"dependencies": {
- "undici-types": "~6.19.2"
+ "undici-types": "~7.14.0"
}
},
"node_modules/@types/pako": {
@@ -753,19 +789,6 @@
"integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==",
"dev": true
},
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "dev": true,
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -832,27 +855,6 @@
"node": ">=8"
}
},
- "node_modules/ansi-escape-sequences": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz",
- "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==",
- "dev": true,
- "dependencies": {
- "array-back": "^3.0.1"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/ansi-escape-sequences/node_modules/array-back": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
- "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -1030,26 +1032,23 @@
}
},
"node_modules/cache-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz",
- "integrity": "sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-3.0.1.tgz",
+ "integrity": "sha512-itTIMLEKbh6Dw5DruXbxAgcyLnh/oPGVLBfTPqBOftASxHe8bAeXy7JkO4F0LvHqht7XqP5O/09h5UcHS2w0FA==",
"dev": true,
"dependencies": {
- "array-back": "^4.0.1",
- "fs-then-native": "^2.0.0",
- "mkdirp2": "^1.0.4"
+ "array-back": "^6.2.2"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/cache-point/node_modules/array-back": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz",
- "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==",
- "dev": true,
- "engines": {
- "node": ">=8"
+ "node": ">=12.17"
+ },
+ "peerDependencies": {
+ "@75lb/nature": "latest"
+ },
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/callsites": {
@@ -1208,19 +1207,6 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/collect-all": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz",
- "integrity": "sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==",
- "dev": true,
- "dependencies": {
- "stream-connect": "^1.0.2",
- "stream-via": "^1.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1246,91 +1232,41 @@
"dev": true
},
"node_modules/command-line-args": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
- "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz",
+ "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==",
"dev": true,
"dependencies": {
- "array-back": "^3.1.0",
- "find-replace": "^3.0.0",
+ "array-back": "^6.2.2",
+ "find-replace": "^5.0.2",
"lodash.camelcase": "^4.3.0",
- "typical": "^4.0.0"
+ "typical": "^7.2.0"
},
"engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/command-line-args/node_modules/array-back": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
- "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/command-line-args/node_modules/typical": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
- "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/command-line-tool": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/command-line-tool/-/command-line-tool-0.8.0.tgz",
- "integrity": "sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==",
- "dev": true,
- "dependencies": {
- "ansi-escape-sequences": "^4.0.0",
- "array-back": "^2.0.0",
- "command-line-args": "^5.0.0",
- "command-line-usage": "^4.1.0",
- "typical": "^2.6.1"
+ "node": ">=12.20"
},
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/command-line-tool/node_modules/array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "dependencies": {
- "typical": "^2.6.1"
+ "peerDependencies": {
+ "@75lb/nature": "latest"
},
- "engines": {
- "node": ">=4"
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/command-line-usage": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz",
- "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==",
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
+ "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
"dev": true,
"dependencies": {
- "ansi-escape-sequences": "^4.0.0",
- "array-back": "^2.0.0",
- "table-layout": "^0.4.2",
- "typical": "^2.6.1"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/command-line-usage/node_modules/array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "dependencies": {
- "typical": "^2.6.1"
+ "array-back": "^6.2.2",
+ "chalk-template": "^0.4.0",
+ "table-layout": "^4.1.0",
+ "typical": "^7.1.1"
},
"engines": {
- "node": ">=4"
+ "node": ">=12.20.0"
}
},
"node_modules/commander": {
@@ -1346,12 +1282,12 @@
"dev": true
},
"node_modules/common-sequence": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.2.tgz",
- "integrity": "sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-3.0.0.tgz",
+ "integrity": "sha512-g/CgSYk93y+a1IKm50tKl7kaT/OjjTYVQlEbUlt/49ZLV1mcKpUU7iyDiqTAeLdb4QDtQfq3ako8y8v//fzrWQ==",
"dev": true,
"engines": {
- "node": ">=8"
+ "node": ">=12.17"
}
},
"node_modules/commondir": {
@@ -1373,23 +1309,32 @@
}
},
"node_modules/compression": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
- "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"dev": true,
"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": {
"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,
+ "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",
@@ -1405,12 +1350,6 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
- "node_modules/compression/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1506,6 +1445,15 @@
"node": ">= 8"
}
},
+ "node_modules/current-module-paths": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/current-module-paths/-/current-module-paths-1.1.2.tgz",
+ "integrity": "sha512-H4s4arcLx/ugbu1XkkgSvcUZax0L6tXUqnppGniQb8l5VjUKGHoayXE5RiriiPhYDd+kjZnaok1Uig13PKtKYQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.17"
+ }
+ },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -1576,26 +1524,29 @@
}
},
"node_modules/dmd": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.2.3.tgz",
- "integrity": "sha512-SIEkjrG7cZ9GWZQYk/mH+mWtcRPly/3ibVuXO/tP/MFoWz6KiRK77tSMq6YQBPl7RljPtXPQ/JhxbNuCdi1bNw==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/dmd/-/dmd-7.1.1.tgz",
+ "integrity": "sha512-Ap2HP6iuOek7eShReDLr9jluNJm9RMZESlt29H/Xs1qrVMkcS9X6m5h1mBC56WMxNiSo0wvjGICmZlYUSFjwZQ==",
"dev": true,
"dependencies": {
"array-back": "^6.2.2",
- "cache-point": "^2.0.0",
- "common-sequence": "^2.0.2",
- "file-set": "^4.0.2",
+ "cache-point": "^3.0.0",
+ "common-sequence": "^3.0.0",
+ "file-set": "^5.2.2",
"handlebars": "^4.7.8",
"marked": "^4.3.0",
- "object-get": "^2.1.1",
- "reduce-flatten": "^3.0.1",
- "reduce-unique": "^2.0.1",
- "reduce-without": "^1.0.1",
- "test-value": "^3.0.0",
- "walk-back": "^5.1.0"
+ "walk-back": "^5.1.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=12.17"
+ },
+ "peerDependencies": {
+ "@75lb/nature": "latest"
+ },
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/duplexify": {
@@ -1780,9 +1731,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
- "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
+ "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,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2010,46 +1961,24 @@
}
},
"node_modules/file-set": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/file-set/-/file-set-4.0.2.tgz",
- "integrity": "sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/file-set/-/file-set-5.3.0.tgz",
+ "integrity": "sha512-FKCxdjLX0J6zqTWdT0RXIxNF/n7MyXXnsSUp0syLEOCKdexvPZ02lNNv2a+gpK9E3hzUYF3+eFZe32ci7goNUg==",
"dev": true,
"dependencies": {
- "array-back": "^5.0.0",
- "glob": "^7.1.6"
+ "array-back": "^6.2.2",
+ "fast-glob": "^3.3.2"
},
"engines": {
- "node": ">=10"
- }
- },
- "node_modules/file-set/node_modules/array-back": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz",
- "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/file-set/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "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"
+ "node": ">=12.17"
},
- "engines": {
- "node": "*"
+ "peerDependencies": {
+ "@75lb/nature": "latest"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/fill-range": {
@@ -2065,24 +1994,20 @@
}
},
"node_modules/find-replace": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
- "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
- "dev": true,
- "dependencies": {
- "array-back": "^3.0.1"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/find-replace/node_modules/array-back": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
- "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz",
+ "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==",
"dev": true,
"engines": {
- "node": ">=6"
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "@75lb/nature": "latest"
+ },
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/find-up": {
@@ -2160,15 +2085,6 @@
"node": ">=6 <7 || >=8"
}
},
- "node_modules/fs-then-native": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz",
- "integrity": "sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==",
- "dev": true,
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -2844,9 +2760,9 @@
}
},
"node_modules/jsdoc": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz",
- "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz",
+ "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.20.15",
@@ -2873,76 +2789,73 @@
}
},
"node_modules/jsdoc-api": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-8.1.1.tgz",
- "integrity": "sha512-yas9E4h8NHp1CTEZiU/DPNAvLoUcip+Hl8Xi1RBYzHqSrgsF+mImAZNtwymrXvgbrgl4bNGBU9syulM0JzFeHQ==",
+ "version": "9.3.5",
+ "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-9.3.5.tgz",
+ "integrity": "sha512-TQwh1jA8xtCkIbVwm/XA3vDRAa5JjydyKx1cC413Sh3WohDFxcMdwKSvn4LOsq2xWyAmOU/VnSChTQf6EF0R8g==",
"dev": true,
"dependencies": {
"array-back": "^6.2.2",
- "cache-point": "^2.0.0",
- "collect-all": "^1.0.4",
- "file-set": "^4.0.2",
- "fs-then-native": "^2.0.0",
- "jsdoc": "^4.0.3",
+ "cache-point": "^3.0.1",
+ "current-module-paths": "^1.1.2",
+ "file-set": "^5.3.0",
+ "jsdoc": "^4.0.4",
"object-to-spawn-args": "^2.0.1",
- "temp-path": "^1.0.0",
- "walk-back": "^5.1.0"
+ "walk-back": "^5.1.1"
},
"engines": {
"node": ">=12.17"
+ },
+ "peerDependencies": {
+ "@75lb/nature": "latest"
+ },
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/jsdoc-parse": {
- "version": "6.2.4",
- "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.4.tgz",
- "integrity": "sha512-MQA+lCe3ioZd0uGbyB3nDCDZcKgKC7m/Ivt0LgKZdUoOlMJxUWJQ3WI6GeyHp9ouznKaCjlp7CU9sw5k46yZTw==",
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.5.tgz",
+ "integrity": "sha512-8JaSNjPLr2IuEY4Das1KM6Z4oLHZYUnjRrr27hKSa78Cj0i5Lur3DzNnCkz+DfrKBDoljGMoWOiBVQbtUZJBPw==",
"dev": true,
"dependencies": {
"array-back": "^6.2.2",
"find-replace": "^5.0.1",
- "lodash.omit": "^4.5.0",
"sort-array": "^5.0.0"
},
"engines": {
"node": ">=12"
}
},
- "node_modules/jsdoc-parse/node_modules/find-replace": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz",
- "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==",
- "dev": true,
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@75lb/nature": "latest"
- },
- "peerDependenciesMeta": {
- "@75lb/nature": {
- "optional": true
- }
- }
- },
"node_modules/jsdoc-to-markdown": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-8.0.3.tgz",
- "integrity": "sha512-JGYYd5xygnQt1DIxH+HUI+X/ynL8qWihzIF0n15NSCNtM6MplzawURRcaLI2WkiS2hIjRIgsphCOfM7FkaWiNg==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-9.1.3.tgz",
+ "integrity": "sha512-i9wi+6WHX0WKziv0ar88T8h7OmxA0LWdQaV23nY6uQyKvdUPzVt0o6YAaOceFuKRF5Rvlju5w/KnZBfdpDAlnw==",
"dev": true,
"dependencies": {
"array-back": "^6.2.2",
- "command-line-tool": "^0.8.0",
+ "command-line-args": "^6.0.1",
+ "command-line-usage": "^7.0.3",
"config-master": "^3.1.0",
- "dmd": "^6.2.3",
- "jsdoc-api": "^8.1.1",
- "jsdoc-parse": "^6.2.1",
- "walk-back": "^5.1.0"
+ "dmd": "^7.1.1",
+ "jsdoc-api": "^9.3.5",
+ "jsdoc-parse": "^6.2.5",
+ "walk-back": "^5.1.1"
},
"bin": {
"jsdoc2md": "bin/cli.js"
},
"engines": {
"node": ">=12.17"
+ },
+ "peerDependencies": {
+ "@75lb/nature": "latest"
+ },
+ "peerDependenciesMeta": {
+ "@75lb/nature": {
+ "optional": true
+ }
}
},
"node_modules/jsdoc/node_modules/escape-string-regexp": {
@@ -3059,18 +2972,6 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
- "node_modules/lodash.omit": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
- "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==",
- "dev": true
- },
- "node_modules/lodash.padend": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
- "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==",
- "dev": true
- },
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -3178,30 +3079,9 @@
}
},
"node_modules/mime-db": {
- "version": "1.53.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
- "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "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,
"engines": {
"node": ">= 0.6"
@@ -3258,12 +3138,6 @@
"node": ">=10"
}
},
- "node_modules/mkdirp2": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz",
- "integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==",
- "dev": true
- },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -3292,9 +3166,9 @@
"dev": true
},
"node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -3318,12 +3192,6 @@
"node": ">=8"
}
},
- "node_modules/object-get": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/object-get/-/object-get-2.1.1.tgz",
- "integrity": "sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==",
- "dev": true
- },
"node_modules/object-to-spawn-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz",
@@ -3334,9 +3202,9 @@
}
},
"node_modules/on-headers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
- "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true,
"engines": {
"node": ">= 0.8"
@@ -3530,6 +3398,50 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz",
+ "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.56.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz",
+ "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3650,61 +3562,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
- "node_modules/reduce-flatten": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz",
- "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/reduce-unique": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/reduce-unique/-/reduce-unique-2.0.1.tgz",
- "integrity": "sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/reduce-without": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/reduce-without/-/reduce-without-1.0.1.tgz",
- "integrity": "sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==",
- "dev": true,
- "dependencies": {
- "test-value": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/reduce-without/node_modules/array-back": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
- "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==",
- "dev": true,
- "dependencies": {
- "typical": "^2.6.0"
- },
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/reduce-without/node_modules/test-value": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz",
- "integrity": "sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==",
- "dev": true,
- "dependencies": {
- "array-back": "^1.0.3",
- "typical": "^2.6.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/registry-auth-token": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
@@ -3969,9 +3826,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,
"dependencies": {
"@zeit/schemas": "2.36.0",
@@ -3981,7 +3838,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"
@@ -4121,9 +3978,9 @@
}
},
"node_modules/sort-array": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.0.0.tgz",
- "integrity": "sha512-Sg9MzajSGprcSrMIxsXyNT0e0JB47RJRfJspC+7co4Z5BdNsNl8FmWI+lXEpyKq+vkMG6pHgAhqyCO+bkDTfFQ==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.1.1.tgz",
+ "integrity": "sha512-EltS7AIsNlAFIM9cayrgKrM6XP94ATWwXP4LCL4IQbvbYhELSt2hZTrixg+AaQwnWFs/JGJgqU3rxMcNNWxGAA==",
"dev": true,
"dependencies": {
"array-back": "^6.2.2",
@@ -4141,15 +3998,6 @@
}
}
},
- "node_modules/sort-array/node_modules/typical": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz",
- "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==",
- "dev": true,
- "engines": {
- "node": ">=12.17"
- }
- },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -4242,46 +4090,12 @@
"escodegen": "^2.1.0"
}
},
- "node_modules/stream-connect": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz",
- "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==",
- "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
- "dev": true,
- "dependencies": {
- "array-back": "^1.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/stream-connect/node_modules/array-back": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
- "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==",
- "dev": true,
- "dependencies": {
- "typical": "^2.6.0"
- },
- "engines": {
- "node": ">=0.12.0"
- }
- },
"node_modules/stream-shift": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
"dev": true
},
- "node_modules/stream-via": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz",
- "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -4436,62 +4250,16 @@
}
},
"node_modules/table-layout": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
- "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==",
- "dev": true,
- "dependencies": {
- "array-back": "^2.0.0",
- "deep-extend": "~0.6.0",
- "lodash.padend": "^4.6.1",
- "typical": "^2.6.1",
- "wordwrapjs": "^3.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/table-layout/node_modules/array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "dependencies": {
- "typical": "^2.6.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/temp-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz",
- "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==",
- "dev": true
- },
- "node_modules/test-value": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz",
- "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
+ "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
"dev": true,
"dependencies": {
- "array-back": "^2.0.0",
- "typical": "^2.6.1"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/test-value/node_modules/array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "dependencies": {
- "typical": "^2.6.1"
+ "array-back": "^6.2.2",
+ "wordwrapjs": "^5.1.0"
},
"engines": {
- "node": ">=4"
+ "node": ">=12.17"
}
},
"node_modules/text-table": {
@@ -4505,18 +4273,9 @@
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"dev": true,
- "dependencies": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
- }
- },
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
- "engines": {
- "node": ">=4"
+ "dependencies": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
}
},
"node_modules/to-regex-range": {
@@ -4577,10 +4336,13 @@
"dev": true
},
"node_modules/typical": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
- "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==",
- "dev": true
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
+ "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.17"
+ }
},
"node_modules/uc.micro": {
"version": "2.1.0",
@@ -4608,9 +4370,9 @@
"dev": true
},
"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.14.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
+ "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"dev": true
},
"node_modules/universalify": {
@@ -4720,25 +4482,12 @@
"dev": true
},
"node_modules/wordwrapjs": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
- "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==",
- "dev": true,
- "dependencies": {
- "reduce-flatten": "^1.0.1",
- "typical": "^2.6.1"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/wordwrapjs/node_modules/reduce-flatten": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz",
- "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz",
+ "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==",
"dev": true,
"engines": {
- "node": ">=0.10.0"
+ "node": ">=12.17"
}
},
"node_modules/wrap-ansi": {
@@ -4927,24 +4676,24 @@
},
"dependencies": {
"@babel/helper-string-parser": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
- "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true
},
"@babel/helper-validator-identifier": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
- "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "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
},
"@babel/parser": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
- "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"requires": {
- "@babel/types": "^7.25.6"
+ "@babel/types": "^7.28.5"
}
},
"@babel/runtime": {
@@ -4954,14 +4703,13 @@
"dev": true
},
"@babel/types": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
- "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"requires": {
- "@babel/helper-string-parser": "^7.24.8",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
}
},
"@choojs/findup": {
@@ -4974,12 +4722,12 @@
}
},
"@eslint-community/eslint-utils": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
- "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "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,
"requires": {
- "eslint-visitor-keys": "^3.3.0"
+ "eslint-visitor-keys": "^3.4.3"
},
"dependencies": {
"eslint-visitor-keys": {
@@ -5121,14 +4869,22 @@
"dev": true
},
"@jsdoc/salty": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz",
- "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==",
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz",
+ "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==",
"dev": true,
"requires": {
"lodash": "^4.17.21"
}
},
+ "@khronosgroup/gltf-interactivity-sample-engine": {
+ "version": "file:../../glTF-InteractivityGraph-AuthoringTool/src/BasicBehaveEngine",
+ "requires": {
+ "@types/node": "^24.1.0",
+ "gl-matrix": "^3.4.3",
+ "typescript": "^5.8.3"
+ }
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -5162,6 +4918,15 @@
"dev": true,
"optional": true
},
+ "@playwright/test": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz",
+ "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==",
+ "dev": true,
+ "requires": {
+ "playwright": "1.56.0"
+ }
+ },
"@rollup/plugin-commonjs": {
"version": "26.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.3.tgz",
@@ -5381,12 +5146,12 @@
"dev": true
},
"@types/node": {
- "version": "22.7.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
- "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
+ "version": "24.7.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
+ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
"dev": true,
"requires": {
- "undici-types": "~6.19.2"
+ "undici-types": "~7.14.0"
}
},
"@types/pako": {
@@ -5406,16 +5171,6 @@
"integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==",
"dev": true
},
- "accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "dev": true,
- "requires": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- }
- },
"acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -5469,23 +5224,6 @@
}
}
},
- "ansi-escape-sequences": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz",
- "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==",
- "dev": true,
- "requires": {
- "array-back": "^3.0.1"
- },
- "dependencies": {
- "array-back": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
- "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
- "dev": true
- }
- }
- },
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -5615,22 +5353,12 @@
"dev": true
},
"cache-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz",
- "integrity": "sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-3.0.1.tgz",
+ "integrity": "sha512-itTIMLEKbh6Dw5DruXbxAgcyLnh/oPGVLBfTPqBOftASxHe8bAeXy7JkO4F0LvHqht7XqP5O/09h5UcHS2w0FA==",
"dev": true,
"requires": {
- "array-back": "^4.0.1",
- "fs-then-native": "^2.0.0",
- "mkdirp2": "^1.0.4"
- },
- "dependencies": {
- "array-back": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz",
- "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==",
- "dev": true
- }
+ "array-back": "^6.2.2"
}
},
"callsites": {
@@ -5742,16 +5470,6 @@
}
}
},
- "collect-all": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz",
- "integrity": "sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==",
- "dev": true,
- "requires": {
- "stream-connect": "^1.0.2",
- "stream-via": "^1.0.4"
- }
- },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -5774,76 +5492,27 @@
"dev": true
},
"command-line-args": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
- "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz",
+ "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==",
"dev": true,
"requires": {
- "array-back": "^3.1.0",
- "find-replace": "^3.0.0",
+ "array-back": "^6.2.2",
+ "find-replace": "^5.0.2",
"lodash.camelcase": "^4.3.0",
- "typical": "^4.0.0"
- },
- "dependencies": {
- "array-back": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
- "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
- "dev": true
- },
- "typical": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
- "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
- "dev": true
- }
- }
- },
- "command-line-tool": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/command-line-tool/-/command-line-tool-0.8.0.tgz",
- "integrity": "sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==",
- "dev": true,
- "requires": {
- "ansi-escape-sequences": "^4.0.0",
- "array-back": "^2.0.0",
- "command-line-args": "^5.0.0",
- "command-line-usage": "^4.1.0",
- "typical": "^2.6.1"
- },
- "dependencies": {
- "array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "requires": {
- "typical": "^2.6.1"
- }
- }
+ "typical": "^7.2.0"
}
},
"command-line-usage": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz",
- "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==",
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
+ "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
"dev": true,
"requires": {
- "ansi-escape-sequences": "^4.0.0",
- "array-back": "^2.0.0",
- "table-layout": "^0.4.2",
- "typical": "^2.6.1"
- },
- "dependencies": {
- "array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "requires": {
- "typical": "^2.6.1"
- }
- }
+ "array-back": "^6.2.2",
+ "chalk-template": "^0.4.0",
+ "table-layout": "^4.1.0",
+ "typical": "^7.1.1"
}
},
"commander": {
@@ -5859,9 +5528,9 @@
"dev": true
},
"common-sequence": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.2.tgz",
- "integrity": "sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-3.0.0.tgz",
+ "integrity": "sha512-g/CgSYk93y+a1IKm50tKl7kaT/OjjTYVQlEbUlt/49ZLV1mcKpUU7iyDiqTAeLdb4QDtQfq3ako8y8v//fzrWQ==",
"dev": true
},
"commondir": {
@@ -5880,20 +5549,26 @@
}
},
"compression": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
- "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"dev": true,
"requires": {
- "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"
},
"dependencies": {
+ "bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -5908,12 +5583,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
}
}
},
@@ -5992,6 +5661,12 @@
"which": "^2.0.1"
}
},
+ "current-module-paths": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/current-module-paths/-/current-module-paths-1.1.2.tgz",
+ "integrity": "sha512-H4s4arcLx/ugbu1XkkgSvcUZax0L6tXUqnppGniQb8l5VjUKGHoayXE5RiriiPhYDd+kjZnaok1Uig13PKtKYQ==",
+ "dev": true
+ },
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -6038,23 +5713,18 @@
}
},
"dmd": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.2.3.tgz",
- "integrity": "sha512-SIEkjrG7cZ9GWZQYk/mH+mWtcRPly/3ibVuXO/tP/MFoWz6KiRK77tSMq6YQBPl7RljPtXPQ/JhxbNuCdi1bNw==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/dmd/-/dmd-7.1.1.tgz",
+ "integrity": "sha512-Ap2HP6iuOek7eShReDLr9jluNJm9RMZESlt29H/Xs1qrVMkcS9X6m5h1mBC56WMxNiSo0wvjGICmZlYUSFjwZQ==",
"dev": true,
"requires": {
"array-back": "^6.2.2",
- "cache-point": "^2.0.0",
- "common-sequence": "^2.0.2",
- "file-set": "^4.0.2",
+ "cache-point": "^3.0.0",
+ "common-sequence": "^3.0.0",
+ "file-set": "^5.2.2",
"handlebars": "^4.7.8",
"marked": "^4.3.0",
- "object-get": "^2.1.1",
- "reduce-flatten": "^3.0.1",
- "reduce-unique": "^2.0.1",
- "reduce-without": "^1.0.1",
- "test-value": "^3.0.0",
- "walk-back": "^5.1.0"
+ "walk-back": "^5.1.1"
}
},
"duplexify": {
@@ -6183,9 +5853,9 @@
}
},
"eslint-visitor-keys": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
- "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
+ "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
},
"espree": {
@@ -6361,35 +6031,13 @@
}
},
"file-set": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/file-set/-/file-set-4.0.2.tgz",
- "integrity": "sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/file-set/-/file-set-5.3.0.tgz",
+ "integrity": "sha512-FKCxdjLX0J6zqTWdT0RXIxNF/n7MyXXnsSUp0syLEOCKdexvPZ02lNNv2a+gpK9E3hzUYF3+eFZe32ci7goNUg==",
"dev": true,
"requires": {
- "array-back": "^5.0.0",
- "glob": "^7.1.6"
- },
- "dependencies": {
- "array-back": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz",
- "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==",
- "dev": true
- },
- "glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
- "requires": {
- "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"
- }
- }
+ "array-back": "^6.2.2",
+ "fast-glob": "^3.3.2"
}
},
"fill-range": {
@@ -6402,21 +6050,11 @@
}
},
"find-replace": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
- "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz",
+ "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==",
"dev": true,
- "requires": {
- "array-back": "^3.0.1"
- },
- "dependencies": {
- "array-back": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
- "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
- "dev": true
- }
- }
+ "requires": {}
},
"find-up": {
"version": "5.0.0",
@@ -6475,12 +6113,6 @@
"universalify": "^0.1.0"
}
},
- "fs-then-native": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz",
- "integrity": "sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==",
- "dev": true
- },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -7022,9 +6654,9 @@
}
},
"jsdoc": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz",
- "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz",
+ "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==",
"dev": true,
"requires": {
"@babel/parser": "^7.20.15",
@@ -7053,56 +6685,45 @@
}
},
"jsdoc-api": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-8.1.1.tgz",
- "integrity": "sha512-yas9E4h8NHp1CTEZiU/DPNAvLoUcip+Hl8Xi1RBYzHqSrgsF+mImAZNtwymrXvgbrgl4bNGBU9syulM0JzFeHQ==",
+ "version": "9.3.5",
+ "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-9.3.5.tgz",
+ "integrity": "sha512-TQwh1jA8xtCkIbVwm/XA3vDRAa5JjydyKx1cC413Sh3WohDFxcMdwKSvn4LOsq2xWyAmOU/VnSChTQf6EF0R8g==",
"dev": true,
"requires": {
"array-back": "^6.2.2",
- "cache-point": "^2.0.0",
- "collect-all": "^1.0.4",
- "file-set": "^4.0.2",
- "fs-then-native": "^2.0.0",
- "jsdoc": "^4.0.3",
+ "cache-point": "^3.0.1",
+ "current-module-paths": "^1.1.2",
+ "file-set": "^5.3.0",
+ "jsdoc": "^4.0.4",
"object-to-spawn-args": "^2.0.1",
- "temp-path": "^1.0.0",
- "walk-back": "^5.1.0"
+ "walk-back": "^5.1.1"
}
},
"jsdoc-parse": {
- "version": "6.2.4",
- "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.4.tgz",
- "integrity": "sha512-MQA+lCe3ioZd0uGbyB3nDCDZcKgKC7m/Ivt0LgKZdUoOlMJxUWJQ3WI6GeyHp9ouznKaCjlp7CU9sw5k46yZTw==",
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.5.tgz",
+ "integrity": "sha512-8JaSNjPLr2IuEY4Das1KM6Z4oLHZYUnjRrr27hKSa78Cj0i5Lur3DzNnCkz+DfrKBDoljGMoWOiBVQbtUZJBPw==",
"dev": true,
"requires": {
"array-back": "^6.2.2",
"find-replace": "^5.0.1",
- "lodash.omit": "^4.5.0",
"sort-array": "^5.0.0"
- },
- "dependencies": {
- "find-replace": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz",
- "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==",
- "dev": true,
- "requires": {}
- }
}
},
"jsdoc-to-markdown": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-8.0.3.tgz",
- "integrity": "sha512-JGYYd5xygnQt1DIxH+HUI+X/ynL8qWihzIF0n15NSCNtM6MplzawURRcaLI2WkiS2hIjRIgsphCOfM7FkaWiNg==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-9.1.3.tgz",
+ "integrity": "sha512-i9wi+6WHX0WKziv0ar88T8h7OmxA0LWdQaV23nY6uQyKvdUPzVt0o6YAaOceFuKRF5Rvlju5w/KnZBfdpDAlnw==",
"dev": true,
"requires": {
"array-back": "^6.2.2",
- "command-line-tool": "^0.8.0",
+ "command-line-args": "^6.0.1",
+ "command-line-usage": "^7.0.3",
"config-master": "^3.1.0",
- "dmd": "^6.2.3",
- "jsdoc-api": "^8.1.1",
- "jsdoc-parse": "^6.2.1",
- "walk-back": "^5.1.0"
+ "dmd": "^7.1.1",
+ "jsdoc-api": "^9.3.5",
+ "jsdoc-parse": "^6.2.5",
+ "walk-back": "^5.1.1"
}
},
"json-buffer": {
@@ -7201,18 +6822,6 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
- "lodash.omit": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
- "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==",
- "dev": true
- },
- "lodash.padend": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
- "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==",
- "dev": true
- },
"lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -7304,28 +6913,11 @@
}
},
"mime-db": {
- "version": "1.53.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
- "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "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
},
- "mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
- "requires": {
- "mime-db": "1.52.0"
- },
- "dependencies": {
- "mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "dev": true
- }
- }
- },
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -7359,12 +6951,6 @@
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
- "mkdirp2": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz",
- "integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==",
- "dev": true
- },
"moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -7390,9 +6976,9 @@
"dev": true
},
"negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"dev": true
},
"neo-async": {
@@ -7410,12 +6996,6 @@
"path-key": "^3.0.0"
}
},
- "object-get": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/object-get/-/object-get-2.1.1.tgz",
- "integrity": "sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==",
- "dev": true
- },
"object-to-spawn-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz",
@@ -7423,9 +7003,9 @@
"dev": true
},
"on-headers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
- "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true
},
"once": {
@@ -7562,6 +7142,31 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
+ "playwright": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz",
+ "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==",
+ "dev": true,
+ "requires": {
+ "fsevents": "2.3.2",
+ "playwright-core": "1.56.0"
+ },
+ "dependencies": {
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "playwright-core": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz",
+ "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==",
+ "dev": true
+ },
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -7647,48 +7252,6 @@
}
}
},
- "reduce-flatten": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz",
- "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==",
- "dev": true
- },
- "reduce-unique": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/reduce-unique/-/reduce-unique-2.0.1.tgz",
- "integrity": "sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==",
- "dev": true
- },
- "reduce-without": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/reduce-without/-/reduce-without-1.0.1.tgz",
- "integrity": "sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==",
- "dev": true,
- "requires": {
- "test-value": "^2.0.0"
- },
- "dependencies": {
- "array-back": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
- "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==",
- "dev": true,
- "requires": {
- "typical": "^2.6.0"
- }
- },
- "test-value": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz",
- "integrity": "sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==",
- "dev": true,
- "requires": {
- "array-back": "^1.0.3",
- "typical": "^2.6.0"
- }
- }
- }
- },
"registry-auth-token": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
@@ -7876,9 +7439,9 @@
"dev": true
},
"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,
"requires": {
"@zeit/schemas": "2.36.0",
@@ -7888,7 +7451,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"
@@ -7992,21 +7555,13 @@
"dev": true
},
"sort-array": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.0.0.tgz",
- "integrity": "sha512-Sg9MzajSGprcSrMIxsXyNT0e0JB47RJRfJspC+7co4Z5BdNsNl8FmWI+lXEpyKq+vkMG6pHgAhqyCO+bkDTfFQ==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.1.1.tgz",
+ "integrity": "sha512-EltS7AIsNlAFIM9cayrgKrM6XP94ATWwXP4LCL4IQbvbYhELSt2hZTrixg+AaQwnWFs/JGJgqU3rxMcNNWxGAA==",
"dev": true,
"requires": {
"array-back": "^6.2.2",
"typical": "^7.1.1"
- },
- "dependencies": {
- "typical": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz",
- "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==",
- "dev": true
- }
}
},
"source-map": {
@@ -8095,38 +7650,12 @@
"escodegen": "^2.1.0"
}
},
- "stream-connect": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz",
- "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==",
- "dev": true,
- "requires": {
- "array-back": "^1.0.2"
- },
- "dependencies": {
- "array-back": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
- "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==",
- "dev": true,
- "requires": {
- "typical": "^2.6.0"
- }
- }
- }
- },
"stream-shift": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
"dev": true
},
- "stream-via": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz",
- "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==",
- "dev": true
- },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -8237,54 +7766,13 @@
"dev": true
},
"table-layout": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
- "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==",
- "dev": true,
- "requires": {
- "array-back": "^2.0.0",
- "deep-extend": "~0.6.0",
- "lodash.padend": "^4.6.1",
- "typical": "^2.6.1",
- "wordwrapjs": "^3.0.0"
- },
- "dependencies": {
- "array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "requires": {
- "typical": "^2.6.1"
- }
- }
- }
- },
- "temp-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz",
- "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==",
- "dev": true
- },
- "test-value": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz",
- "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
+ "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
"dev": true,
"requires": {
- "array-back": "^2.0.0",
- "typical": "^2.6.1"
- },
- "dependencies": {
- "array-back": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
- "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
- "dev": true,
- "requires": {
- "typical": "^2.6.1"
- }
- }
+ "array-back": "^6.2.2",
+ "wordwrapjs": "^5.1.0"
}
},
"text-table": {
@@ -8303,12 +7791,6 @@
"xtend": "~4.0.1"
}
},
- "to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true
- },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -8352,9 +7834,9 @@
"dev": true
},
"typical": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
- "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
+ "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
"dev": true
},
"uc.micro": {
@@ -8377,9 +7859,9 @@
"dev": true
},
"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.14.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
+ "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"dev": true
},
"universalify": {
@@ -8464,22 +7946,10 @@
"dev": true
},
"wordwrapjs": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
- "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==",
- "dev": true,
- "requires": {
- "reduce-flatten": "^1.0.1",
- "typical": "^2.6.1"
- },
- "dependencies": {
- "reduce-flatten": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz",
- "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==",
- "dev": true
- }
- }
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz",
+ "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==",
+ "dev": true
},
"wrap-ansi": {
"version": "8.1.0",
diff --git a/package.json b/package.json
index 7b8283f7..bf8f40c3 100644
--- a/package.json
+++ b/package.json
@@ -12,12 +12,13 @@
"scripts": {
"build": "rollup -c",
"watch": "rollup -cw",
+ "testApp": "npm run build & npx serve ./dist",
+ "test": "npx playwright test",
"prepublishOnly": "npm run build && npm run build_docs",
- "build_docs": "jsdoc2md source/gltf-sample-renderer.js source/GltfView/gltf_view.js source/GltfState/gltf_state.js source/ResourceLoader/resource_loader.js source/gltf/user_camera.js > API.md",
- "test": "echo \"Error: no test specified\" && exit 1",
+ "build_docs": "jsdoc2md source/gltf-sample-renderer.js source/GltfView/gltf_view.js source/GltfState/gltf_state.js source/GltfState/animation_timer.js source/ResourceLoader/resource_loader.js source/gltf/user_camera.js source/gltf/interactivity.js > API.md",
"lint": "eslint source/**/*.js",
"lint:fix": "eslint --fix source/**/*.js",
- "prettier": "npx prettier source/**/*.js --check",
+ "prettier": "npx prettier source/**/*.js tests/**/*.js tests/**/*.ts --check",
"prettier:fix": "npm run prettier -- --write",
"format": "npm run prettier:fix && npm run lint:fix"
},
@@ -29,6 +30,7 @@
"author": "Khronos Group Inc.",
"license": "Apache-2.0",
"dependencies": {
+ "@khronosgroup/gltf-interactivity-sample-engine": "file:../../glTF-InteractivityGraph-AuthoringTool/src/BasicBehaveEngine",
"fast-png": "^6.2.0",
"gl-matrix": "^3.2.1",
"globals": "^15.5.0",
@@ -36,22 +38,24 @@
"json-ptr": "^3.1.0"
},
"devDependencies": {
+ "@playwright/test": "^1.56.0",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-wasm": "^6.2.2",
+ "@types/node": "^24.7.2",
"concurrently": "^8.2.2",
"eslint": "^9.5.0",
"eslint-config-prettier": "^10.1.8",
- "jsdoc-to-markdown": "^8.0.1",
+ "jsdoc-to-markdown": "^9.1.3",
"prettier": "3.6.2",
"rollup": "^4.23.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-glslify": "^1.3.1",
"rollup-plugin-license": "^3.5.2",
- "serve": "^14.2.4"
+ "serve": "^14.2.5"
},
"bugs": {
"url": "https://github.com/KhronosGroup/glTF-Sample-Renderer/issues"
},
"homepage": "https://github.com/KhronosGroup/glTF-Sample-Renderer/#readme"
-}
+}
\ No newline at end of file
diff --git a/playwright.config.js b/playwright.config.js
new file mode 100644
index 00000000..8e3e35d2
--- /dev/null
+++ b/playwright.config.js
@@ -0,0 +1,95 @@
+// @ts-check
+import { defineConfig, devices } from "@playwright/test";
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// import dotenv from 'dotenv';
+// import path from 'path';
+// dotenv.config({ path: path.resolve(__dirname, '.env') });
+
+/**
+ * @see https://playwright.dev/docs/test-configuration
+ */
+export default defineConfig({
+ testDir: "./tests",
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: "html",
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('')`. */
+ baseURL: 'http://localhost:3000',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: "on-first-retry",
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: "download",
+ testMatch: "**/downloadAssets.spec.ts",
+ use: { downloadFolder: "interactivity", testRepoURL: "https://raw.githubusercontent.com/KhronosGroup/glTF-Test-Assets-Interactivity/refs/heads/main/Tests/Interactivity/test-index.json" },
+ },
+ {
+ name: "download math",
+ testMatch: "**/downloadAssets.spec.ts",
+ use: { downloadFolder: "interactivity_math", testRepoURL: "https://raw.githubusercontent.com/KhronosGroup/glTF-Test-Assets-Interactivity/refs/heads/main/Tests/Interactivity/mathtests-index.json" },
+ },
+ {
+ name: "chromium",
+ use: { ...devices["Desktop Chrome"] },
+ dependencies: ["download", "download math"],
+ testIgnore: "**/downloadAssets.spec.ts",
+ },
+ {
+ name: "firefox",
+ use: { ...devices["Desktop Firefox"] },
+ dependencies: ["download"],
+ testIgnore: "**/downloadAssets.spec.ts",
+ },
+
+ {
+ name: "webkit",
+ use: { ...devices["Desktop Safari"] },
+ dependencies: ["download"],
+ testIgnore: "**/downloadAssets.spec.ts",
+ },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: { ...devices['Pixel 5'] },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: { ...devices['iPhone 12'] },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: {
+ command: 'npm run testApp',
+ url: 'http://localhost:3000',
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/rollup.config.js b/rollup.config.js
index 2203a1f3..61ea8a4f 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -27,7 +27,7 @@ export default {
resolve({
browser: true,
preferBuiltins: false,
- dedupe: ['gl-matrix', 'jpeg-js', 'fast-png']
+ dedupe: ['gl-matrix', 'jpeg-js', 'fast-png', '@khronosgroup/khr_interactivity_authoring_engine']
}),
copy({
targets: [
@@ -38,7 +38,8 @@ export default {
"assets/images/lut_sheen_E.png",
], dest: "dist/assets"
},
- { src: ["source/libs/*", "!source/libs/hdrpng.js"], dest: "dist/libs" }
+ { src: ["source/libs/*", "!source/libs/hdrpng.js"], dest: "dist/libs" },
+ { src: "tests/testApp/*", dest: "dist"}
]
}),
commonjs(),
diff --git a/source/GltfState/animation_timer.js b/source/GltfState/animation_timer.js
new file mode 100644
index 00000000..ff62f054
--- /dev/null
+++ b/source/GltfState/animation_timer.js
@@ -0,0 +1,69 @@
+/**
+ * AnimationTimer class to control animation playback.
+ */
+class AnimationTimer {
+ constructor() {
+ this.startTime = 0;
+ this.paused = true;
+ this.fixedTime = null;
+ this.pausedTime = 0;
+ }
+
+ /** Start the animation timer and all animations */
+ start() {
+ this.startTime = performance.now();
+ this.paused = false;
+ }
+
+ /** Pause all animations */
+ pause() {
+ this.pausedTime = performance.now() - this.startTime;
+ this.paused = true;
+ }
+
+ /** Unpause all animations */
+ unpause() {
+ this.startTime += performance.now() - this.startTime - this.pausedTime;
+ this.paused = false;
+ }
+
+ /** Toggle the animation playback state */
+ toggle() {
+ if (this.paused) {
+ this.unpause();
+ } else {
+ this.pause();
+ }
+ }
+
+ /** Reset the animation timer. If animations were playing, they will be restarted. */
+ reset() {
+ if (!this.paused) {
+ // Animation is running.
+ this.startTime = performance.now();
+ } else {
+ this.startTime = 0;
+ }
+ this.pausedTime = 0;
+ }
+
+ /**
+ * Plays all animations starting from the specified time
+ * @param {number} timeInSec The time in seconds to set the animation timer to
+ */
+ setFixedTime(timeInSec) {
+ this.paused = false;
+ this.fixedTime = timeInSec;
+ }
+
+ /** Get the elapsed time in seconds */
+ elapsedSec() {
+ if (this.paused) {
+ return this.pausedTime / 1000;
+ } else {
+ return this.fixedTime || (performance.now() - this.startTime) / 1000;
+ }
+ }
+}
+
+export { AnimationTimer };
diff --git a/source/GltfState/gltf_state.js b/source/GltfState/gltf_state.js
index d4b76038..696ea300 100644
--- a/source/GltfState/gltf_state.js
+++ b/source/GltfState/gltf_state.js
@@ -1,5 +1,6 @@
+import { GraphController } from "../gltf/interactivity.js";
import { UserCamera } from "../gltf/user_camera.js";
-import { AnimationTimer } from "../gltf/utils.js";
+import { AnimationTimer } from "./animation_timer.js";
/**
* GltfState containing a state for visualization in GltfView
@@ -31,6 +32,30 @@ class GltfState {
/** KHR_materials_variants */
this.variant = undefined;
+ /** the graph controller allows selecting and playing graphs from KHR_interactivity */
+ this.graphController = new GraphController();
+
+ /** callback for selection: (selectionInfo : {
+ * node,
+ * position,
+ * rayOrigin,
+ * controller }) => {} */
+ this.selectionCallback = undefined;
+
+ /** callback for hovering: (hoverInfo : { node, controller }) => {} */
+ this.hoverCallback = undefined;
+
+ /** If the renderer should compute selection information in the next frame. Is automatically reset after the frame is rendered */
+ this.triggerSelection = false;
+ /** If the renderer should compute hover information in the next frame. */
+ this.enableHover = false;
+
+ /* Array of screen positions for selection. Currently only one is supported. */
+ this.selectionPositions = [{ x: undefined, y: undefined }];
+
+ /* Array of screen positions for hovering. Currently only one is supported. */
+ this.hoverPositions = [{ x: undefined, y: undefined }];
+
/** parameters used to configure the rendering */
this.renderingParameters = {
/** morphing between vertices */
@@ -38,16 +63,17 @@ class GltfState {
/** skin / skeleton */
skinning: true,
+ /** enabled extensions */
enabledExtensions: {
- /** KHR_materials_clearcoat */
+ /** KHR_materials_clearcoat adds a clear coat layer on top of the glTF base material */
KHR_materials_clearcoat: true,
- /** KHR_materials_sheen */
+ /** KHR_materials_sheen adds a sheen layer on top of the glTF base material */
KHR_materials_sheen: true,
- /** KHR_materials_transmission */
+ /** KHR_materials_transmission adds physical-based transparency */
KHR_materials_transmission: true,
- /** KHR_materials_volume */
+ /** KHR_materials_volume adds support for volumetric materials. Used together with KHR_materials_transmission and KHR_materials_diffuse_transmission */
KHR_materials_volume: true,
- /** KHR_materials_volume_scatter */
+ /** KHR_materials_volume_scatter allows the simulation of scattering light inside a volume. Used together with KHR_materials_volume */
KHR_materials_volume_scatter: true,
/** KHR_materials_ior makes the index of refraction configurable */
KHR_materials_ior: true,
@@ -55,12 +81,22 @@ class GltfState {
KHR_materials_specular: true,
/** KHR_materials_iridescence adds a thin-film iridescence effect */
KHR_materials_iridescence: true,
+ /** KHR_materials_diffuse_transmission allows light to pass diffusely through the material */
KHR_materials_diffuse_transmission: true,
/** KHR_materials_anisotropy defines microfacet grooves in the surface, stretching the specular reflection on the surface */
KHR_materials_anisotropy: true,
- /** KHR_materials_dispersion defines configuring the strength of the angular separation of colors (chromatic abberation)*/
+ /** KHR_materials_dispersion defines configuring the strength of the angular separation of colors (chromatic abberation) */
KHR_materials_dispersion: true,
- KHR_materials_emissive_strength: true
+ /** KHR_materials_emissive_strength enables emissive factors larger than 1.0 */
+ KHR_materials_emissive_strength: true,
+ /** KHR_interactivity enables execution of a behavior graph */
+ KHR_interactivity: true,
+ /** KHR_node_hoverability enables hovering over nodes */
+ KHR_node_hoverability: true,
+ /** KHR_node_selectability enables selecting nodes */
+ KHR_node_selectability: true,
+ /** KHR_node_visibility enables controlling the visibility of nodes */
+ KHR_node_visibility: true
},
/** clear color expressed as list of ints in the range [0, 255] */
clearColor: [58, 64, 74, 255],
diff --git a/source/GltfView/gltf_view.js b/source/GltfView/gltf_view.js
index 69f4f75e..6ed85c12 100644
--- a/source/GltfView/gltf_view.js
+++ b/source/GltfView/gltf_view.js
@@ -74,6 +74,10 @@ class GltfView {
return;
}
+ if (state.graphController?.playing) {
+ state.graphController.simulateTick();
+ }
+
scene.applyTransformHierarchy(state.gltf);
this.renderer.drawScene(state, scene);
@@ -100,7 +104,10 @@ class GltfView {
transparentMaterialsCount: 0
};
}
- const nodes = scene.gatherNodes(state.gltf);
+ const nodes = scene.gatherNodes(
+ state.gltf,
+ state.renderingParameters.enabledExtensions
+ ).nodes;
const activeMeshes = nodes
.filter((node) => node.mesh !== undefined)
.map((node) => state.gltf.meshes[node.mesh]);
@@ -159,31 +166,48 @@ class GltfView {
}
_animate(state) {
- if (state.gltf === undefined) {
+ if (state.gltf === undefined || state.gltf.animations === undefined) {
return;
}
-
- if (state.gltf.animations !== undefined && state.animationIndices !== undefined) {
- const disabledAnimations = state.gltf.animations.filter((anim, index) => {
+ let disabledAnimations = [];
+ let enabledAnimations = [];
+
+ if (
+ state.gltf?.extensions?.KHR_interactivity !== undefined &&
+ state.renderingParameters.enabledExtensions.KHR_interactivity
+ ) {
+ if (state.graphController.playing) {
+ for (const animation of state.gltf.animations) {
+ if (animation.createdTimestamp !== undefined) {
+ enabledAnimations.push(animation);
+ }
+ }
+ }
+ } else if (state.animationIndices !== undefined) {
+ disabledAnimations = state.gltf.animations.filter((anim, index) => {
return false === state.animationIndices.includes(index);
});
-
- for (const disabledAnimation of disabledAnimations) {
- disabledAnimation.advance(state.gltf, undefined);
- }
-
- const t = state.animationTimer.elapsedSec();
-
- const animations = state.animationIndices
+ enabledAnimations = state.animationIndices
.map((index) => {
return state.gltf.animations[index];
})
.filter((animation) => animation !== undefined);
-
- for (const animation of animations) {
- animation.advance(state.gltf, t);
+ for (const animation of enabledAnimations) {
+ if (animation.createdTimestamp !== undefined) {
+ animation.reset();
+ }
}
}
+
+ for (const disabledAnimation of disabledAnimations) {
+ disabledAnimation.advance(state.gltf, undefined);
+ }
+
+ const t = state.animationTimer.elapsedSec();
+
+ for (const animation of enabledAnimations) {
+ animation.advance(state.gltf, t);
+ }
}
}
diff --git a/source/Renderer/renderer.js b/source/Renderer/renderer.js
index ca0cbc51..4c41ccdf 100644
--- a/source/Renderer/renderer.js
+++ b/source/Renderer/renderer.js
@@ -1,10 +1,12 @@
-import { mat4, mat3, vec3, quat } from "gl-matrix";
+import { mat4, mat3, vec3, quat, vec4 } from "gl-matrix";
import { ShaderCache } from "./shader_cache.js";
import { GltfState } from "../GltfState/gltf_state.js";
import { gltfWebGl, GL } from "./webgl.js";
import { EnvironmentRenderer } from "./environment_renderer.js";
import pbrShader from "./shaders/pbr.frag";
+import pickingShader from "./shaders/picking.frag";
+import pickingVertShader from "./shaders/picking.vert";
import brdfShader from "./shaders/brdf.glsl";
import iridescenceShader from "./shaders/iridescence.glsl";
import materialInfoShader from "./shaders/material_info.glsl";
@@ -38,6 +40,11 @@ class gltfRenderer {
this.opaqueRenderTexture = 0;
this.opaqueFramebuffer = 0;
this.opaqueDepthTexture = 0;
+ this.pickingIDTexture = 0;
+ this.pickingPositionTexture = 0;
+ this.pickingDepthTexture = 0;
+ this.hoverIDTexture = 0;
+ this.hoverDepthTexture = 0;
this.opaqueFramebufferWidth = 1024;
this.opaqueFramebufferHeight = 1024;
@@ -48,6 +55,8 @@ class gltfRenderer {
const shaderSources = new Map();
shaderSources.set("primitive.vert", primitiveShader);
shaderSources.set("pbr.frag", pbrShader);
+ shaderSources.set("picking.frag", pickingShader);
+ shaderSources.set("picking.vert", pickingVertShader);
shaderSources.set("material_info.glsl", materialInfoShader);
shaderSources.set("brdf.glsl", brdfShader);
shaderSources.set("iridescence.glsl", iridescenceShader);
@@ -152,7 +161,50 @@ class gltfRenderer {
context.framebufferTexture2D(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.TEXTURE_2D, this.scatterDepthTexture, 0);
context.drawBuffers([context.COLOR_ATTACHMENT0]);
+ this.pickingIDTexture = context.createTexture();
+ context.bindTexture(context.TEXTURE_2D, this.pickingIDTexture);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);
+ context.texImage2D(context.TEXTURE_2D, 0, context.R32UI, 1, 1, 0, context.RED_INTEGER, context.UNSIGNED_INT, null);
+ context.bindTexture(context.TEXTURE_2D, null);
+
+ this.pickingPositionTexture = context.createTexture();
+ context.bindTexture(context.TEXTURE_2D, this.pickingPositionTexture);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);
+ context.texImage2D(context.TEXTURE_2D, 0, context.R32UI, 1, 1, 0, context.RED_INTEGER, context.UNSIGNED_INT, null);
+ context.bindTexture(context.TEXTURE_2D, null);
+
+ this.pickingDepthTexture = context.createTexture();
+ context.bindTexture(context.TEXTURE_2D, this.pickingDepthTexture);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);
+ context.texImage2D( context.TEXTURE_2D, 0, context.DEPTH_COMPONENT24, 1, 1, 0, context.DEPTH_COMPONENT, context.UNSIGNED_INT, null);
+ context.bindTexture(context.TEXTURE_2D, null);
+
+ this.hoverIDTexture = context.createTexture();
+ context.bindTexture(context.TEXTURE_2D, this.hoverIDTexture);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);
+ context.texImage2D(context.TEXTURE_2D, 0, context.R32UI, 1, 1, 0, context.RED_INTEGER, context.UNSIGNED_INT, null);
+ context.bindTexture(context.TEXTURE_2D, null);
+ this.hoverDepthTexture = context.createTexture();
+ context.bindTexture(context.TEXTURE_2D, this.hoverDepthTexture);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);
+ context.texImage2D( context.TEXTURE_2D, 0, context.DEPTH_COMPONENT24, 1, 1, 0, context.DEPTH_COMPONENT, context.UNSIGNED_INT, null);
+ context.bindTexture(context.TEXTURE_2D, null);
this.colorRenderBuffer = context.createRenderbuffer();
context.bindRenderbuffer(context.RENDERBUFFER, this.colorRenderBuffer);
@@ -165,6 +217,18 @@ class gltfRenderer {
context.DEPTH_COMPONENT24,
this.opaqueFramebufferWidth,
this.opaqueFramebufferHeight);
+
+ this.pickingFramebuffer = context.createFramebuffer();
+ context.bindFramebuffer(context.FRAMEBUFFER, this.pickingFramebuffer);
+ context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT0, context.TEXTURE_2D, this.pickingIDTexture, 0);
+ context.framebufferTexture2D(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.TEXTURE_2D, this.pickingDepthTexture, 0);
+ context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT1, context.TEXTURE_2D, this.pickingPositionTexture, 0);
+ context.drawBuffers([context.COLOR_ATTACHMENT0, context.COLOR_ATTACHMENT1]);
+
+ this.hoverFramebuffer = context.createFramebuffer();
+ context.bindFramebuffer(context.FRAMEBUFFER, this.hoverFramebuffer);
+ context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT0, context.TEXTURE_2D, this.hoverIDTexture, 0);
+ context.framebufferTexture2D(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.TEXTURE_2D, this.hoverDepthTexture, 0);
this.samples = samples;
@@ -272,18 +336,58 @@ class gltfRenderer {
this.webGl.context.clearColor(...clearColor);
this.webGl.context.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
this.webGl.context.bindFramebuffer(this.webGl.context.FRAMEBUFFER, null);
+ this.webGl.context.bindFramebuffer(this.webGl.context.FRAMEBUFFER, this.pickingFramebuffer);
+ this.webGl.context.clearBufferuiv(GL.COLOR, 0, new Uint32Array([0, 0, 0, 0]));
+ this.webGl.context.clearBufferuiv(GL.COLOR, 1, new Uint32Array([0, 0, 0, 0]));
+ this.webGl.context.clearBufferfv(GL.DEPTH, 0, new Float32Array([1.0]));
+ this.webGl.context.bindFramebuffer(this.webGl.context.FRAMEBUFFER, null);
+ this.webGl.context.bindFramebuffer(this.webGl.context.FRAMEBUFFER, this.hoverFramebuffer);
+ this.webGl.context.clearBufferuiv(GL.COLOR, 0, new Uint32Array([0, 0, 0, 0]));
+ this.webGl.context.clearBufferfv(GL.DEPTH, 0, new Float32Array([1.0]));
+ this.webGl.context.bindFramebuffer(this.webGl.context.FRAMEBUFFER, null);
}
prepareScene(state, scene) {
- this.nodes = scene.gatherNodes(state.gltf);
+ const newNodes = scene.gatherNodes(state.gltf, state.renderingParameters.enabledExtensions);
+ this.selectionDrawables = newNodes.selectableNodes
+ .filter((node) => node.mesh !== undefined)
+ .reduce(
+ (accumulator, node) =>
+ accumulator.concat(
+ state.gltf.meshes[node.mesh].primitives.map((primitive, index) => {
+ return { node: node, primitive: primitive, primitiveIndex: index };
+ })
+ ),
+ []
+ );
+ this.hoverDrawables = newNodes.hoverableNodes
+ .filter((node) => node.mesh !== undefined)
+ .reduce(
+ (accumulator, node) =>
+ accumulator.concat(
+ state.gltf.meshes[node.mesh].primitives.map((primitive, index) => {
+ return { node: node, primitive: primitive, primitiveIndex: index };
+ })
+ ),
+ []
+ );
+
+ // check if nodes have changed since previous frame to avoid unnecessary updates
+ if (
+ newNodes.nodes.length === this.nodes?.length &&
+ newNodes.nodes.every((element, i) => element === this.nodes[i])
+ ) {
+ return;
+ }
+ this.nodes = newNodes.nodes;
// collect drawables by essentially zipping primitives (for geometry and material)
// and nodes for the transform
const drawables = this.nodes
.filter((node) => node.mesh !== undefined)
.reduce(
- (acc, node) =>
- acc.concat(
+ (accumulator, node) =>
+ accumulator.concat(
state.gltf.meshes[node.mesh].primitives.map((primitive, index) => {
return { node: node, primitive: primitive, primitiveIndex: index };
})
@@ -291,6 +395,7 @@ class gltfRenderer {
[]
)
.filter(({ primitive }) => primitive.material !== undefined);
+ this.drawables = drawables;
// opaque drawables don't need sorting
this.opaqueDrawables = drawables.filter(
@@ -355,10 +460,7 @@ class gltfRenderer {
// render complete gltf scene with given camera
drawScene(state, scene) {
- if (this.preparedScene !== scene) {
- this.prepareScene(state, scene);
- this.preparedScene = scene;
- }
+ this.prepareScene(state, scene);
let currentCamera = undefined;
@@ -405,7 +507,7 @@ class gltfRenderer {
this.viewMatrix = currentCamera.getViewMatrix(state.gltf);
this.currentCameraPosition = currentCamera.getPosition(state.gltf);
- this.visibleLights = this.getVisibleLights(state.gltf, scene.nodes);
+ this.visibleLights = this.getVisibleLights(state.gltf, this.nodes);
if (
this.visibleLights.length === 0 &&
!state.renderingParameters.useIBL &&
@@ -483,6 +585,82 @@ class gltfRenderer {
return;
}
+ let pickingProjection = undefined;
+ let pickingViewProjection = mat4.create();
+
+ let pickingX = state.selectionPositions[0].x;
+ let pickingY = state.selectionPositions[0].y;
+
+ // Draw a 1x1 texture for picking
+ if (state.triggerSelection && pickingX !== undefined && pickingY !== undefined) {
+ pickingProjection = currentCamera.getProjectionMatrixForPixel(
+ pickingX - aspectOffsetX,
+ this.currentHeight - pickingY - aspectOffsetY,
+ aspectWidth,
+ aspectHeight
+ );
+ mat4.multiply(pickingViewProjection, pickingProjection, this.viewMatrix);
+ this.webGl.context.bindFramebuffer(
+ this.webGl.context.FRAMEBUFFER,
+ this.pickingFramebuffer
+ );
+ this.webGl.context.viewport(0, 0, 1, 1);
+
+ for (const drawable of this.selectionDrawables) {
+ let renderpassConfiguration = {};
+ renderpassConfiguration.picking = true;
+ this.drawPrimitive(
+ state,
+ renderpassConfiguration,
+ drawable.primitive,
+ drawable.node,
+ pickingViewProjection
+ );
+ }
+ }
+
+ pickingX = state.hoverPositions[0].x;
+ pickingY = state.hoverPositions[0].y;
+
+ const needsHover = state.graphController.needsHover();
+ const calcHoverInfo =
+ (state.enableHover || needsHover) && pickingX !== undefined && pickingY !== undefined;
+
+ // Draw a 1x1 texture for hover
+ if (calcHoverInfo) {
+ // We do not need to recalculate the picking projection matrix if selection and hover use the same position
+ if (
+ pickingProjection === undefined ||
+ pickingX !== state.selectionPositions[0].x ||
+ pickingY !== state.selectionPositions[0].y
+ ) {
+ pickingProjection = currentCamera.getProjectionMatrixForPixel(
+ pickingX - aspectOffsetX,
+ this.currentHeight - pickingY - aspectOffsetY,
+ aspectWidth,
+ aspectHeight
+ );
+ mat4.multiply(pickingViewProjection, pickingProjection, this.viewMatrix);
+ }
+ this.webGl.context.bindFramebuffer(
+ this.webGl.context.FRAMEBUFFER,
+ this.hoverFramebuffer
+ );
+ this.webGl.context.viewport(0, 0, 1, 1);
+
+ for (const drawable of this.hoverDrawables) {
+ let renderpassConfiguration = {};
+ renderpassConfiguration.picking = true;
+ this.drawPrimitive(
+ state,
+ renderpassConfiguration,
+ drawable.primitive,
+ drawable.node,
+ pickingViewProjection
+ );
+ }
+ }
+
// If any transmissive drawables are present, render all opaque and transparent drawables into a separate framebuffer.
if (this.transmissionDrawables.length > 0) {
// Render transmission sample texture
@@ -662,6 +840,156 @@ class gltfRenderer {
this.viewProjectionMatrix
);
}
+
+ // Handle selection
+ if (state.triggerSelection) {
+ this.webGl.context.bindFramebuffer(
+ this.webGl.context.FRAMEBUFFER,
+ this.pickingFramebuffer
+ );
+ this.webGl.context.viewport(0, 0, 1, 1);
+ state.triggerSelection = false;
+ this.webGl.context.readBuffer(this.webGl.context.COLOR_ATTACHMENT0);
+
+ // Read pixel under controller (e.g. mouse cursor), which contains the picking ID
+ const pixels = new Uint32Array(1);
+ this.webGl.context.readPixels(
+ 0,
+ 0,
+ 1,
+ 1,
+ this.webGl.context.RED_INTEGER,
+ this.webGl.context.UNSIGNED_INT,
+ pixels
+ );
+
+ // Compute ray origin in world space. This is the near plane position of the current pixel.
+ pickingX = state.selectionPositions[0].x;
+ pickingY = state.selectionPositions[0].y;
+ const x = pickingX - aspectOffsetX;
+ const y = this.currentHeight - pickingY - aspectOffsetY;
+ const nearPlane = currentCamera.getNearPlaneForPixel(
+ x + 0.5,
+ y + 0.5,
+ aspectWidth,
+ aspectHeight
+ );
+ const zNear = currentCamera.perspective?.znear
+ ? currentCamera.perspective.znear
+ : currentCamera.orthographic.znear;
+ let rayOrigin = vec3.fromValues(nearPlane.left, nearPlane.bottom, -zNear);
+ vec3.transformMat4(rayOrigin, rayOrigin, currentCamera.getTransformMatrix(state.gltf));
+
+ let pickingResult = {
+ node: undefined,
+ position: undefined,
+ rayOrigin: rayOrigin,
+ controller: 0
+ };
+
+ // Search for node with matching picking ID
+ let found = false;
+ for (const node of state.gltf.nodes) {
+ if (node.pickingColor === pixels[0]) {
+ found = true;
+ pickingResult.node = node;
+ break;
+ }
+ }
+
+ // If a node was found, we need to calculate the ray intersection position
+ if (found) {
+ // WebGL does not allow reading from depth buffer
+ this.webGl.context.readBuffer(this.webGl.context.COLOR_ATTACHMENT1);
+ const position = new Uint32Array(1);
+ this.webGl.context.readPixels(
+ 0,
+ 0,
+ 1,
+ 1,
+ this.webGl.context.RED_INTEGER,
+ this.webGl.context.UNSIGNED_INT,
+ position
+ );
+
+ // Transform uint to float [-1, 1] in clip space
+ const z = (position[0] / 4294967295) * 2.0 - 1.0;
+
+ // Get view space position
+ const clipSpacePosition = vec4.fromValues(0, 0, z, 1);
+ vec4.transformMat4(
+ clipSpacePosition,
+ clipSpacePosition,
+ mat4.invert(mat4.create(), pickingProjection)
+ );
+
+ // Divide by w to get normalized device coordinates
+ vec4.divide(
+ clipSpacePosition,
+ clipSpacePosition,
+ vec4.fromValues(
+ clipSpacePosition[3],
+ clipSpacePosition[3],
+ clipSpacePosition[3],
+ clipSpacePosition[3]
+ )
+ );
+ const worldPos = vec4.transformMat4(
+ vec4.create(),
+ clipSpacePosition,
+ mat4.invert(mat4.create(), this.viewMatrix)
+ );
+ pickingResult.position = vec3.fromValues(worldPos[0], worldPos[1], worldPos[2]);
+ }
+
+ // Send picking result to Interactivity engine
+ state.graphController.receiveSelection(pickingResult);
+
+ if (state.selectionCallback) {
+ state.selectionCallback(pickingResult);
+ }
+ }
+
+ if (calcHoverInfo) {
+ this.webGl.context.bindFramebuffer(
+ this.webGl.context.FRAMEBUFFER,
+ this.hoverFramebuffer
+ );
+ this.webGl.context.viewport(0, 0, 1, 1);
+ this.webGl.context.readBuffer(this.webGl.context.COLOR_ATTACHMENT0);
+
+ // Read pixel under controller (e.g. mouse cursor), which contains the picking ID
+ const pixels = new Uint32Array(1);
+ this.webGl.context.readPixels(
+ 0,
+ 0,
+ 1,
+ 1,
+ this.webGl.context.RED_INTEGER,
+ this.webGl.context.UNSIGNED_INT,
+ pixels
+ );
+
+ let pickingResult = {
+ node: undefined,
+ controller: 0
+ };
+
+ // Search for node with matching picking ID
+ for (const node of state.gltf.nodes) {
+ if (node.pickingColor === pixels[0]) {
+ pickingResult.node = node;
+ break;
+ }
+ }
+
+ // Send picking result to Interactivity engine
+ state.graphController.receiveHover(pickingResult);
+
+ if (state.enableHover && state.hoverCallback) {
+ state.hoverCallback(pickingResult);
+ }
+ }
}
// vertices with given material
@@ -671,9 +999,9 @@ class gltfRenderer {
if (primitive.skip) return;
let material;
- if(primitive.mappings !== undefined && state.variant != "default")
+ if(primitive.mappings !== undefined && state.variant != "default" && state.gltf.extensions?.KHR_materials_variants.variants !== undefined)
{
- const names = state.gltf.variants.map(obj => obj.name);
+ const names = state.gltf.extensions.KHR_materials_variants.variants.map(obj => obj.name);
const idx = names.indexOf(state.variant);
let materialIdx = primitive.material;
primitive.mappings.forEach(element => {
@@ -730,15 +1058,18 @@ class gltfRenderer {
this.pushFragParameterDefines(fragDefines, state);
+ const vertexShader = renderpassConfiguration.picking ? "picking.vert" : "primitive.vert";
let fragmentShader = "pbr.frag";
if (material.type === "SG") {
fragmentShader = "specular_glossiness.frag";
} else if (renderpassConfiguration.scatter) {
fragmentShader = "scatter.frag";
+ } else if (renderpassConfiguration.picking) {
+ fragmentShader = "picking.frag";
}
const fragmentHash = this.shaderCache.selectShader(fragmentShader, fragDefines);
- const vertexHash = this.shaderCache.selectShader("primitive.vert", vertDefines);
+ const vertexHash = this.shaderCache.selectShader(vertexShader, vertDefines);
if (fragmentHash && vertexHash)
{
@@ -752,7 +1083,7 @@ class gltfRenderer {
this.webGl.context.useProgram(this.shader.program);
- if (state.renderingParameters.usePunctual)
+ if (state.renderingParameters.usePunctual && !renderpassConfiguration.picking)
{
this.applyLights();
}
@@ -767,7 +1098,9 @@ class gltfRenderer {
this.shader.updateUniform("u_NormalMatrix", node.normalMatrix, false);
this.shader.updateUniform("u_Exposure", state.renderingParameters.exposure, false);
this.shader.updateUniform("u_Camera", this.currentCameraPosition, false);
-
+ if (renderpassConfiguration.picking) {
+ this.shader.updateUniform("u_PickingColor", node.pickingColor, false);
+ }
this.updateAnimationUniforms(state, node, primitive);
@@ -780,7 +1113,7 @@ class gltfRenderer {
this.webGl.context.frontFace(GL.CCW);
}
- if (material.doubleSided)
+ if (material.doubleSided || renderpassConfiguration.picking)
{
this.webGl.context.disable(GL.CULL_FACE);
}
@@ -789,7 +1122,7 @@ class gltfRenderer {
this.webGl.context.enable(GL.CULL_FACE);
}
- if (material.alphaMode === 'BLEND')
+ if (material.alphaMode === 'BLEND' && !renderpassConfiguration.picking)
{
this.webGl.context.enable(GL.BLEND);
this.webGl.context.blendFuncSeparate(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA);
@@ -813,6 +1146,9 @@ class gltfRenderer {
let vertexCount = 0;
for (const attribute of primitive.glAttributes)
{
+ if (renderpassConfiguration.picking && (attribute.attribute !== "POSITION" || attribute.attribute.startsWith("JOINTS") || attribute.attribute.startsWith("WEIGHTS"))) {
+ continue;
+ }
const gltfAccessor = state.gltf.accessors[attribute.accessor];
vertexCount = gltfAccessor.count;
@@ -965,51 +1301,53 @@ class gltfRenderer {
textureIndex++;
}
- let textureCount = textureIndex;
-
- textureCount = this.applyEnvironmentMap(state, textureCount);
-
-
- if (state.environment !== undefined)
- {
- this.webGl.setTexture(this.shader.getUniformLocation("u_SheenELUT"), state.environment, state.environment.sheenELUT, textureCount++);
- }
-
- if (material.hasVolumeScatter && sampledTextures?.scatterSampleTexture !== undefined)
- {
- this.webGl.context.activeTexture(GL.TEXTURE0 + textureCount);
- this.webGl.context.bindTexture(this.webGl.context.TEXTURE_2D, sampledTextures.scatterSampleTexture);
- this.webGl.context.uniform1i(this.shader.getUniformLocation("u_ScatterFramebufferSampler"), textureCount);
- textureCount++;
-
- this.webGl.context.activeTexture(GL.TEXTURE0 + textureCount);
- this.webGl.context.bindTexture(this.webGl.context.TEXTURE_2D, sampledTextures.scatterDepthSampleTexture);
- this.webGl.context.uniform1i(this.shader.getUniformLocation("u_ScatterDepthFramebufferSampler"), textureCount);
- textureCount++;
-
- this.webGl.context.uniform1f(this.shader.getUniformLocation("u_MinRadius"), gltfMaterial.scatterMinRadius);
- this.webGl.context.uniform2i(this.shader.getUniformLocation("u_FramebufferSize"), renderpassConfiguration.frameBufferSize[0], renderpassConfiguration.frameBufferSize[1]);
- this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ProjectionMatrix"),false, this.projMatrix);
-
- this.shader.updateUniformArray("u_ScatterSamples", gltfMaterial.scatterSamples);
- }
+ if (!renderpassConfiguration.picking) {
+ let textureCount = textureIndex;
- if(sampledTextures?.transmissionSampleTexture !== undefined &&
- state.environment &&
- state.renderingParameters.enabledExtensions.KHR_materials_transmission)
- {
- this.webGl.context.activeTexture(GL.TEXTURE0 + textureCount);
- this.webGl.context.bindTexture(this.webGl.context.TEXTURE_2D, this.opaqueRenderTexture);
- this.webGl.context.uniform1i(this.shader.getUniformLocation("u_TransmissionFramebufferSampler"), textureCount);
- textureCount++;
+ textureCount = this.applyEnvironmentMap(state, textureCount);
- this.webGl.context.uniform2i(this.shader.getUniformLocation("u_TransmissionFramebufferSize"), this.opaqueFramebufferWidth, this.opaqueFramebufferHeight);
- this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ModelMatrix"),false, node.worldTransform);
- this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ViewMatrix"),false, this.viewMatrix);
- this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ProjectionMatrix"),false, this.projMatrix);
- }
+ if (state.environment !== undefined)
+ {
+ this.webGl.setTexture(this.shader.getUniformLocation("u_SheenELUT"), state.environment, state.environment.sheenELUT, textureCount++);
+ }
+ if (material.hasVolumeScatter && sampledTextures?.scatterSampleTexture !== undefined)
+ {
+ this.webGl.context.activeTexture(GL.TEXTURE0 + textureCount);
+ this.webGl.context.bindTexture(this.webGl.context.TEXTURE_2D, sampledTextures.scatterSampleTexture);
+ this.webGl.context.uniform1i(this.shader.getUniformLocation("u_ScatterFramebufferSampler"), textureCount);
+ textureCount++;
+
+ this.webGl.context.activeTexture(GL.TEXTURE0 + textureCount);
+ this.webGl.context.bindTexture(this.webGl.context.TEXTURE_2D, sampledTextures.scatterDepthSampleTexture);
+ this.webGl.context.uniform1i(this.shader.getUniformLocation("u_ScatterDepthFramebufferSampler"), textureCount);
+ textureCount++;
+
+ this.webGl.context.uniform1f(this.shader.getUniformLocation("u_MinRadius"), gltfMaterial.scatterMinRadius);
+ this.webGl.context.uniform2i(this.shader.getUniformLocation("u_FramebufferSize"), renderpassConfiguration.frameBufferSize[0], renderpassConfiguration.frameBufferSize[1]);
+ this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ProjectionMatrix"),false, this.projMatrix);
+
+ this.shader.updateUniformArray("u_ScatterSamples", gltfMaterial.scatterSamples);
+ }
+
+ if(sampledTextures?.transmissionSampleTexture !== undefined &&
+ state.environment &&
+ state.renderingParameters.enabledExtensions.KHR_materials_transmission)
+ {
+ this.webGl.context.activeTexture(GL.TEXTURE0 + textureCount);
+ this.webGl.context.bindTexture(this.webGl.context.TEXTURE_2D, this.opaqueRenderTexture);
+ this.webGl.context.uniform1i(this.shader.getUniformLocation("u_TransmissionFramebufferSampler"), textureCount);
+ textureCount++;
+
+ this.webGl.context.uniform2i(this.shader.getUniformLocation("u_TransmissionFramebufferSize"), this.opaqueFramebufferWidth, this.opaqueFramebufferHeight);
+
+ this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ModelMatrix"),false, node.worldTransform);
+ this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ViewMatrix"),false, this.viewMatrix);
+ this.webGl.context.uniformMatrix4fv(this.shader.getUniformLocation("u_ProjectionMatrix"),false, this.projMatrix);
+ }
+ }
+
if (drawIndexed)
{
const indexAccessor = state.gltf.accessors[primitive.indices];
@@ -1030,6 +1368,9 @@ class gltfRenderer {
for (const attribute of primitive.glAttributes)
{
+ if (renderpassConfiguration.picking && (attribute.attribute !== "POSITION" || attribute.attribute.startsWith("JOINTS") || attribute.attribute.startsWith("WEIGHTS"))) {
+ continue;
+ }
const location = this.shader.getAttributeLocation(attribute.name);
if (location === null)
{
@@ -1054,18 +1395,12 @@ class gltfRenderer {
getVisibleLights(gltf, nodes) {
let nodeLights = [];
- for (const nodeIndex of nodes) {
- const node = gltf.nodes[nodeIndex];
-
- if (node.children !== undefined) {
- nodeLights = nodeLights.concat(this.getVisibleLights(gltf, node.children));
- }
-
+ for (const node of nodes) {
const lightIndex = node.extensions?.KHR_lights_punctual?.light;
if (lightIndex === undefined) {
continue;
}
- const light = gltf.lights[lightIndex];
+ const light = gltf.extensions?.KHR_lights_punctual?.lights[lightIndex];
nodeLights.push([node, light]);
}
diff --git a/source/Renderer/shader.js b/source/Renderer/shader.js
index f33ff1a7..d57bd54a 100644
--- a/source/Renderer/shader.js
+++ b/source/Renderer/shader.js
@@ -164,6 +164,29 @@ class gltfShader {
this.gl.context.uniform4iv(uniform.loc, value);
break;
+ case GL.UNSIGNED_INT: {
+ if (
+ Array.isArray(value) ||
+ value instanceof Uint32Array ||
+ value instanceof Int32Array
+ ) {
+ this.gl.context.uniform1uiv(uniform.loc, value);
+ } else {
+ this.gl.context.uniform1ui(uniform.loc, value);
+ }
+ break;
+ }
+
+ case GL.UNSIGNED_INT_VEC2:
+ this.gl.context.uniform2uiv(uniform.loc, value);
+ break;
+ case GL.UNSIGNED_INT_VEC3:
+ this.gl.context.uniform3uiv(uniform.loc, value);
+ break;
+ case GL.UNSIGNED_INT_VEC4:
+ this.gl.context.uniform4uiv(uniform.loc, value);
+ break;
+
case GL.FLOAT_MAT2:
this.gl.context.uniformMatrix2fv(uniform.loc, false, value);
break;
diff --git a/source/Renderer/shaders/picking.frag b/source/Renderer/shaders/picking.frag
new file mode 100644
index 00000000..b7e358b9
--- /dev/null
+++ b/source/Renderer/shaders/picking.frag
@@ -0,0 +1,11 @@
+precision highp float;
+
+layout(location = 0) out uint id_color;
+layout(location = 1) out uint position;
+
+uniform uint u_PickingColor;
+
+void main() {
+ id_color = u_PickingColor;
+ position = uint(gl_FragCoord.z * 4294967295.0); // mapping [0, 1] to uint
+}
diff --git a/source/Renderer/shaders/picking.vert b/source/Renderer/shaders/picking.vert
new file mode 100644
index 00000000..7939c0ad
--- /dev/null
+++ b/source/Renderer/shaders/picking.vert
@@ -0,0 +1,34 @@
+#include