From 20117bffa64fa3e199738eafdf49bd8734ae284e Mon Sep 17 00:00:00 2001 From: Bela Bohlender Date: Mon, 2 Sep 2024 03:01:59 +0200 Subject: [PATCH] feat: speeding up intersection traversal for multiple points --- examples/handheld-ar/app.tsx | 1 - examples/rag-doll/src/App.jsx | 2 +- examples/uikit/app.tsx | 2 +- examples/uikit/package.json | 2 +- packages/pointer-events/README.md | 72 +- packages/pointer-events/bench.json | 317 +++++++ .../pointer-events/bench/pointer.bench.ts | 80 +- packages/pointer-events/package.json | 7 +- packages/pointer-events/src/combine.ts | 127 ++- packages/pointer-events/src/forward.ts | 31 +- .../pointer-events/src/intersections/index.ts | 4 +- .../src/intersections/intersector.ts | 26 + .../pointer-events/src/intersections/lines.ts | 201 +++-- .../pointer-events/src/intersections/ray.ts | 301 +++---- .../src/intersections/sphere.ts | 166 ++-- .../pointer-events/src/intersections/utils.ts | 68 +- packages/pointer-events/src/pointer.ts | 31 +- packages/pointer-events/src/pointer/grab.ts | 31 +- packages/pointer-events/src/pointer/index.ts | 1 + packages/pointer-events/src/pointer/lines.ts | 43 + packages/pointer-events/src/pointer/ray.ts | 72 +- packages/pointer-events/src/pointer/touch.ts | 42 +- packages/react/xr/src/default.tsx | 3 +- packages/react/xr/src/pointer.tsx | 79 +- packages/react/xr/src/xr.tsx | 23 +- packages/xr/src/vanilla/default.ts | 101 +-- packages/xr/src/vanilla/elements.ts | 40 +- pnpm-lock.yaml | 796 ++++++++---------- 28 files changed, 1524 insertions(+), 1145 deletions(-) create mode 100644 packages/pointer-events/bench.json create mode 100644 packages/pointer-events/src/intersections/intersector.ts create mode 100644 packages/pointer-events/src/pointer/lines.ts diff --git a/examples/handheld-ar/app.tsx b/examples/handheld-ar/app.tsx index a1c1343a..88bc6381 100644 --- a/examples/handheld-ar/app.tsx +++ b/examples/handheld-ar/app.tsx @@ -9,7 +9,6 @@ export function App() { const [bool, setBool] = useState(false) return ( <> - diff --git a/examples/rag-doll/src/App.jsx b/examples/rag-doll/src/App.jsx index 4ee1ba49..87f95a0b 100644 --- a/examples/rag-doll/src/App.jsx +++ b/examples/rag-doll/src/App.jsx @@ -36,7 +36,7 @@ export function App() { > Enter VR - + diff --git a/examples/uikit/app.tsx b/examples/uikit/app.tsx index 7d205fc2..aa0e9500 100644 --- a/examples/uikit/app.tsx +++ b/examples/uikit/app.tsx @@ -23,7 +23,7 @@ const store = createXRStore({ controller: { teleportPointer: true }, }) -setPreferredColorScheme('light') +setPreferredColorScheme('dark') export function App() { const [counter, setCounter] = useState(0) diff --git a/examples/uikit/package.json b/examples/uikit/package.json index 53cf6c1b..4bbcd0ff 100644 --- a/examples/uikit/package.json +++ b/examples/uikit/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@pmndrs/pointer-events": "^6.1.0", + "@pmndrs/pointer-events": "workspace:^", "@react-three/drei": "^9.108.3", "@react-three/uikit": "^0.4.0", "@react-three/uikit-default": "^0.4.0", diff --git a/packages/pointer-events/README.md b/packages/pointer-events/README.md index f2eaaafc..b2a7b601 100644 --- a/packages/pointer-events/README.md +++ b/packages/pointer-events/README.md @@ -1,34 +1,35 @@ # pointer-events -*framework agnostic pointer-events implementation for three.js* +_framework agnostic pointer-events implementation for three.js_ based on [🎯 Designing Pointer-events for 3D & XR](https://polar.sh/bbohlender/posts/designing-pointer-events-for-3d) ## How to use ```js -import * as THREE from 'three'; -import { forwardHtmlEvents } from '@pmndrs/pointer-events'; +import * as THREE from 'three' +import { forwardHtmlEvents } from '@pmndrs/pointer-events' -const canvas = document.getElementById("canvas") -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 ); -camera.position.z = 1; +const canvas = document.getElementById('canvas') +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10) +camera.position.z = 1 forwardHtmlEvents(canvas, camera, scene) -const width = window.innerWidth, height = window.innerHeight; +const width = window.innerWidth, + height = window.innerHeight -const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 ); -const material = new THREE.MeshBasicMaterial({ color: new THREE.Color("red") }); -const mesh = new THREE.Mesh( geometry, material ); -scene.add( mesh ); +const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2) +const material = new THREE.MeshBasicMaterial({ color: new THREE.Color('red') }) +const mesh = new THREE.Mesh(geometry, material) +scene.add(mesh) -mesh.addEventListener("pointerover", () => material.color.set("blue")) -mesh.addEventListener("pointerout", () => material.color.set("red")) +mesh.addEventListener('pointerover', () => material.color.set('blue')) +mesh.addEventListener('pointerout', () => material.color.set('red')) -const renderer = new THREE.WebGLRenderer( { antialias: true } ); -renderer.setSize( width, height ); -renderer.setAnimationLoop(() => renderer.render( scene, camera )); +const renderer = new THREE.WebGLRenderer({ antialias: true }) +renderer.setSize(width, height) +renderer.setAnimationLoop(() => renderer.render(scene, camera)) ``` ## Filtering @@ -36,13 +37,44 @@ renderer.setAnimationLoop(() => renderer.render( scene, camera )); Based on the css `pointer-events` property, the behavior of pointer events can be configured with the values `none`, `listener`, or `auto`. ```js -object.pointerEvents = "none"; +object.pointerEvents = 'none' ``` -The values `none` and `auto` correspond to the css properties, where `none` means that an object is not directly targetted and `auto` means the object is always targetted for events. The additional value `listener`, which is the default value, expresses that the object is only targetted by events if the object has any listeners. In 3D scenes this default is more reasonable than `auto`, which is the default in the web, because 3D scenes often contain semi-transparent content, such as particles, that should not catch pointer events by default. +The values `none` and `auto` correspond to the css properties, where `none` means that an object is not directly targetted and `auto` means the object is always targetted for events. The additional value `listener`, which is the default value, expresses that the object is only targetted by events if the object has any listeners. In 3D scenes this default is more reasonable than `auto`, which is the default in the web, because 3D scenes often contain semi-transparent content, such as particles, that should not catch pointer events by default. In addition to the `pointerEvents` property, each 3D object can also filter events based on the `pointerType` with the `pointerEventsType` property. This property defaults to the value `all`, which expresses that pointer events from pointers of all types should be accepted. To filter specific pointer types, such as `screen-mouse`, which represents a normal mouse used through a 2D screen, `pointerEventsType` can be set to `{ allow: "screen-mouse" }` or `{ deny: "screen-touch" }`. `pointerEventsType`'s `allow` and `deny` accept strings and array of strings. In case more custom logic is needed, `pointerEventsType` also accepts a function. In general the pointer types `screen-touch`, `screen-pen`, `ray`, `grab`, and `touch` are used by default. For pointer events that were forwarded through a portal using `forwardObjectEvents`, their `pointerType` is prefixed with `forward-`, while events forwarded from the dom to the scene are prefixed with `screen-`. ## But wait ... there's more -Create your own `Pointer` that can represent a WebXR controller or something else. These `Pointer` can use a normal `Ray` for intersection, or a set of `Lines`, or even a `Sphere`, for grab and touch events. \ No newline at end of file +Create your own `Pointer` that can represent a WebXR controller or something else. These `Pointer` can use a normal `Ray` for intersection, or a set of `Lines`, or even a `Sphere`, for grab and touch events. + +## Performance + +In some cases multi-modal interactivity requires multiple pointers at the same time. Executing `pointer.move`, such as in the following example, can lead to performance issues because the scene graph will be traversed several times. + +```ts +leftGrabPointer.move() +leftTouchPointer.move() +leftRayPointer.move() +rightGrabPointer.move() +rightTouchPointer.move() +rightRayPointer.move() +``` + +In this case, performance can be improved by combining the pointer using `CombinedPointer`, which will traverse the scene graph once per combined pointer, calculating the intersections for each pointer on each object. + +```ts +const leftPointer = new CombinedPointer() +const rightPointer = new CombinedPointer() +leftPointer.register(leftGrabPointer) +leftPointer.register(leftTouchPointer) +leftPointer.register(leftRayPointer) +rightPointer.register(rightGrabPointer) +rightPointer.register(rightTouchPointer) +rightPointer.register(rightRayPointer) + +leftPointer.move() +rightPointer.move() +``` + + diff --git a/packages/pointer-events/bench.json b/packages/pointer-events/bench.json new file mode 100644 index 00000000..cf9c42ef --- /dev/null +++ b/packages/pointer-events/bench.json @@ -0,0 +1,317 @@ +{ + "files": [ + { + "filepath": "/Users/bela/Documents/react-three-xr/packages/pointer-events/bench/pointer.bench.ts", + "groups": [ + { + "fullName": "bench/pointer.bench.ts > pointer performance - scene: horizontal small, depth small; ", + "benchmarks": [ + { + "id": "79624589_0_0", + "sampleCount": 779998, + "median": 0.0005840007215738297, + "name": "raycast", + "rank": 1, + "rme": 0.8471168336591087, + "totalTime": 500.00043648295105, + "min": 0.000499999150633812, + "max": 1.5005419999361038, + "hz": 1559994.6381778733, + "period": 0.0006410278442803072, + "mean": 0.0006410278442803072, + "variance": 0.000005987172335275317, + "sd": 0.0024468699056703685, + "sem": 0.0000027705381517043778, + "df": 779997, + "critical": 1.96, + "moe": 0.00000543025477734058, + "p75": 0.0006250012665987015, + "p99": 0.0008749999105930328, + "p995": 0.001540999859571457, + "p999": 0.005000000819563866 + }, + { + "id": "79624589_0_1", + "sampleCount": 612686, + "median": 0.0007920004427433014, + "name": "scene: horizontal small, depth small; pointer: 1", + "rank": 2, + "rme": 0.1109617626407028, + "totalTime": 500.000562928617, + "min": 0.0006659999489784241, + "max": 0.1528329998254776, + "hz": 1225370.6204076225, + "period": 0.0008160796279474593, + "mean": 0.0008160796279474593, + "variance": 1.3077887015412361e-7, + "sd": 0.0003616336131419805, + "sem": 4.6200833659295424e-7, + "df": 612685, + "critical": 1.96, + "moe": 9.055363397221903e-7, + "p75": 0.000832999125123024, + "p99": 0.0010000001639127731, + "p995": 0.001042000949382782, + "p999": 0.004750000312924385 + }, + { + "id": "79624589_0_2", + "sampleCount": 55542, + "median": 0.008542001247406006, + "name": "scene: horizontal small, depth small; pointer: 10", + "rank": 3, + "rme": 0.804308760642608, + "totalTime": 500.00141398608685, + "min": 0.008332999423146248, + "max": 1.9242499992251396, + "hz": 111083.68585842743, + "period": 0.00900222199391608, + "mean": 0.00900222199391608, + "variance": 0.00007579741705230284, + "sd": 0.008706171205087966, + "sem": 0.000036941663341613623, + "df": 55541, + "critical": 1.96, + "moe": 0.0000724056601495627, + "p75": 0.008666999638080597, + "p99": 0.01616699993610382, + "p995": 0.01854199916124344, + "p999": 0.02483299933373928 + } + ] + }, + { + "fullName": "bench/pointer.bench.ts > pointer performance - scene: horizontal big, depth small; ", + "benchmarks": [ + { + "id": "79624589_1_0", + "sampleCount": 43538, + "median": 0.011374998837709427, + "name": "raycast", + "rank": 2, + "rme": 0.07245784844516281, + "totalTime": 500.00335705280304, + "min": 0.011124998331069946, + "max": 0.0287919994443655, + "hz": 87075.41536646555, + "period": 0.011484297787055056, + "mean": 0.011484297787055056, + "variance": 7.847586125994608e-7, + "sd": 0.0008858660240687983, + "sem": 0.000004245548513028336, + "df": 43537, + "critical": 1.96, + "moe": 0.00000832127508553554, + "p75": 0.011416999623179436, + "p99": 0.01599999889731407, + "p995": 0.01895900070667267, + "p999": 0.02170800045132637 + }, + { + "id": "79624589_1_1", + "sampleCount": 69638, + "median": 0.006999999284744263, + "name": "scene: horizontal big, depth small; pointer: 1", + "rank": 1, + "rme": 0.2154313920305359, + "totalTime": 500.0005480274558, + "min": 0.006791999563574791, + "max": 0.1862080004066229, + "hz": 139275.84734602345, + "period": 0.007179995807281309, + "mean": 0.007179995807281309, + "variance": 0.000004337110664779157, + "sd": 0.00208257308749997, + "sem": 0.000007891818834367469, + "df": 69637, + "critical": 1.96, + "moe": 0.00001546796491536024, + "p75": 0.0070420000702142715, + "p99": 0.011667000129818916, + "p995": 0.015166999772191048, + "p999": 0.020416000857949257 + }, + { + "id": "79624589_1_2", + "sampleCount": 6882, + "median": 0.07091700099408627, + "name": "scene: horizontal big, depth small; pointer: 10", + "rank": 3, + "rme": 0.19838987754517293, + "totalTime": 500.0238889977336, + "min": 0.07016599923372269, + "max": 0.2481249999254942, + "hz": 13763.34241508848, + "period": 0.07265676968871455, + "mean": 0.07265676968871455, + "variance": 0.00003722148662414967, + "sd": 0.006100941453919196, + "sem": 0.00007354269204781589, + "df": 6881, + "critical": 1.96, + "moe": 0.00014414367641371912, + "p75": 0.07137500122189522, + "p99": 0.08487500064074993, + "p995": 0.08879199996590614, + "p999": 0.174040999263525 + } + ] + }, + { + "fullName": "bench/pointer.bench.ts > pointer performance - scene: horizontal small, depth big; ", + "benchmarks": [ + { + "id": "79624589_2_0", + "sampleCount": 696248, + "median": 0.0007080007344484329, + "name": "raycast", + "rank": 1, + "rme": 0.09905234693677462, + "totalTime": 500.0004452355206, + "min": 0.0005829986184835434, + "max": 0.039750000461936, + "hz": 1392494.7600237413, + "period": 0.0007181355569215575, + "mean": 0.0007181355569215575, + "variance": 9.170525419503222e-8, + "sd": 0.00030282875391057605, + "sem": 3.629235323052448e-7, + "df": 696247, + "critical": 1.96, + "moe": 7.113301233182798e-7, + "p75": 0.0007089991122484207, + "p99": 0.0008749999105930328, + "p995": 0.0009170006960630417, + "p999": 0.0053329989314079285 + }, + { + "id": "79624589_2_1", + "sampleCount": 442936, + "median": 0.0011249985545873642, + "name": "scene: horizontal small, depth big; pointer: 1", + "rank": 2, + "rme": 0.09556498217781953, + "totalTime": 500.00061293691397, + "min": 0.0010000001639127731, + "max": 0.03291599825024605, + "hz": 885870.9140340316, + "period": 0.0011288326370783002, + "mean": 0.0011288326370783002, + "variance": 1.3417929869662885e-7, + "sd": 0.00036630492584270396, + "sem": 5.503921981843305e-7, + "df": 442935, + "critical": 1.96, + "moe": 0.0000010787687084412878, + "p75": 0.0011250004172325134, + "p99": 0.0013749990612268448, + "p995": 0.0014589987695217133, + "p999": 0.00708400085568428 + }, + { + "id": "79624589_2_2", + "sampleCount": 42205, + "median": 0.011499999091029167, + "name": "scene: horizontal small, depth big; pointer: 10", + "rank": 3, + "rme": 0.27438848726733067, + "totalTime": 500.0083638317883, + "min": 0.011250000447034836, + "max": 0.1632500011473894, + "hz": 84408.58804153626, + "period": 0.011847135738225052, + "mean": 0.011847135738225052, + "variance": 0.000011609413003957174, + "sd": 0.0034072588695250577, + "sem": 0.000016585294151338278, + "df": 42204, + "critical": 1.96, + "moe": 0.000032507176536623026, + "p75": 0.011541999876499176, + "p99": 0.020124999806284904, + "p995": 0.021667001768946648, + "p999": 0.030917000025510788 + } + ] + }, + { + "fullName": "bench/pointer.bench.ts > pointer performance - scene: horizontal big, depth big; ", + "benchmarks": [ + { + "id": "79624589_3_0", + "sampleCount": 32552, + "median": 0.015167001634836197, + "name": "raycast", + "rank": 2, + "rme": 0.08773686675544771, + "totalTime": 500.01470297016203, + "min": 0.01491600088775158, + "max": 0.09145800024271011, + "hz": 65102.085611955525, + "period": 0.015360490998100332, + "mean": 0.015360490998100332, + "variance": 0.0000015390047923634153, + "sd": 0.0012405663192120828, + "sem": 0.000006875925265298893, + "df": 32551, + "critical": 1.96, + "moe": 0.00001347681351998583, + "p75": 0.015250001102685928, + "p99": 0.020083999261260033, + "p995": 0.024000000208616257, + "p999": 0.02775000035762787 + }, + { + "id": "79624589_3_1", + "sampleCount": 35705, + "median": 0.013583000749349594, + "name": "scene: horizontal big, depth big; pointer: 1", + "rank": 1, + "rme": 0.2997963614088367, + "totalTime": 500.0011429209262, + "min": 0.013333000242710114, + "max": 0.18479100055992603, + "hz": 71409.83676840644, + "period": 0.01400367295675469, + "mean": 0.01400367295675469, + "variance": 0.000016381494831438196, + "sd": 0.004047405938553507, + "sem": 0.000021419643871399908, + "df": 35704, + "critical": 1.96, + "moe": 0.00004198250198794382, + "p75": 0.013667000457644463, + "p99": 0.022499999031424522, + "p995": 0.023041998967528343, + "p999": 0.10487499833106995 + }, + { + "id": "79624589_3_2", + "sampleCount": 3529, + "median": 0.1368749998509884, + "name": "scene: horizontal big, depth big; pointer: 10", + "rank": 3, + "rme": 0.3561966830471971, + "totalTime": 500.0115560181439, + "min": 0.13541699945926666, + "max": 0.3710830006748438, + "hz": 7057.836879017939, + "period": 0.1416864709600861, + "mean": 0.1416864709600861, + "variance": 0.0002339785386001409, + "sd": 0.01529635703689414, + "sem": 0.0002574910764726821, + "df": 3528, + "critical": 1.96, + "moe": 0.0005046825098864568, + "p75": 0.1409579999744892, + "p99": 0.23529099859297276, + "p995": 0.24774999916553497, + "p999": 0.30150000005960464 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/pointer-events/bench/pointer.bench.ts b/packages/pointer-events/bench/pointer.bench.ts index afe9bda3..d2505ea1 100644 --- a/packages/pointer-events/bench/pointer.bench.ts +++ b/packages/pointer-events/bench/pointer.bench.ts @@ -1,29 +1,46 @@ import { bench, describe } from 'vitest' import { createRayPointer } from '../src/pointer/index.js' import { BoxGeometry, Mesh, Object3D, Raycaster, Scene } from 'three' +import { CombinedPointer } from '../src/combine.js' -const tenPointer = new Array(10).fill(undefined).map(() => - createRayPointer( - { - current: new Object3D(), - }, - {}, +const combinedPointer = new CombinedPointer(true) +const leftPointer = new CombinedPointer(false) +combinedPointer.register(leftPointer) +new Array(5).fill(undefined).forEach(() => + leftPointer.register( + createRayPointer( + { + current: new Object3D(), + }, + {}, + ), ), ) - -const onePointer = new Array(1).fill(undefined).map(() => - createRayPointer( - { - current: new Object3D(), - }, - {}, +const rightPointer = new CombinedPointer(false) +combinedPointer.register(rightPointer) +new Array(5).fill(undefined).forEach(() => + rightPointer.register( + createRayPointer( + { + current: new Object3D(), + }, + {}, + ), ), ) +const singlePointer = createRayPointer( + { + current: new Object3D(), + }, + {}, +) + function createScene(hor: 'small' | 'big', depth: 'small' | 'big'): Scene { const scene = new Scene() const horLength = hor === 'small' ? 1 : 20 const depthLength = depth === 'small' ? 1 : 20 + let hasPointerEvents = true for (let i = 0; i < horLength; i++) { const object = new Object3D() object.position.x = 1000 @@ -32,8 +49,11 @@ function createScene(hor: 'small' | 'big', depth: 'small' | 'big'): Scene { for (let i = 0; i < depthLength; i++) { object.add((lastObject = new Object3D())) } - lastObject.add(new Mesh(new BoxGeometry())) + const mesh = new Mesh(new BoxGeometry()) + mesh.pointerEvents = hasPointerEvents ? 'auto' : 'listener' + lastObject.add(mesh) scene.add(object) + hasPointerEvents = !hasPointerEvents } return scene } @@ -49,14 +69,10 @@ describe('pointer performance - scene: horizontal small, depth small; ', () => { raycaster.intersectObject(sceneHorSmallDepthSmall) }) bench('scene: horizontal small, depth small; pointer: 1', () => { - for (const pointer of onePointer) { - pointer.move(sceneHorSmallDepthSmall, { timeStamp: performance.now() }) - } + singlePointer.move(sceneHorSmallDepthSmall, { timeStamp: performance.now() }) }) bench('scene: horizontal small, depth small; pointer: 10', () => { - for (const pointer of tenPointer) { - pointer.move(sceneHorSmallDepthSmall, { timeStamp: performance.now() }) - } + combinedPointer.move(sceneHorSmallDepthSmall, { timeStamp: performance.now() }) }) }) @@ -65,14 +81,10 @@ describe('pointer performance - scene: horizontal big, depth small; ', () => { raycaster.intersectObject(sceneHorBigDepthSmall) }) bench('scene: horizontal big, depth small; pointer: 1', () => { - for (const pointer of onePointer) { - pointer.move(sceneHorBigDepthSmall, { timeStamp: performance.now() }) - } + singlePointer.move(sceneHorBigDepthSmall, { timeStamp: performance.now() }) }) bench('scene: horizontal big, depth small; pointer: 10', () => { - for (const pointer of tenPointer) { - pointer.move(sceneHorBigDepthSmall, { timeStamp: performance.now() }) - } + combinedPointer.move(sceneHorBigDepthSmall, { timeStamp: performance.now() }) }) }) @@ -81,14 +93,10 @@ describe('pointer performance - scene: horizontal small, depth big; ', () => { raycaster.intersectObject(sceneHorSmallDepthBig) }) bench('scene: horizontal small, depth big; pointer: 1', () => { - for (const pointer of onePointer) { - pointer.move(sceneHorSmallDepthBig, { timeStamp: performance.now() }) - } + singlePointer.move(sceneHorSmallDepthBig, { timeStamp: performance.now() }) }) bench('scene: horizontal small, depth big; pointer: 10', () => { - for (const pointer of tenPointer) { - pointer.move(sceneHorSmallDepthBig, { timeStamp: performance.now() }) - } + combinedPointer.move(sceneHorSmallDepthBig, { timeStamp: performance.now() }) }) }) @@ -97,13 +105,9 @@ describe('pointer performance - scene: horizontal big, depth big; ', () => { raycaster.intersectObject(sceneHorBigDepthBig) }) bench('scene: horizontal big, depth big; pointer: 1', () => { - for (const pointer of onePointer) { - pointer.move(sceneHorBigDepthBig, { timeStamp: performance.now() }) - } + singlePointer.move(sceneHorBigDepthBig, { timeStamp: performance.now() }) }) bench('scene: horizontal big, depth big; pointer: 10', () => { - for (const pointer of tenPointer) { - pointer.move(sceneHorBigDepthBig, { timeStamp: performance.now() }) - } + combinedPointer.move(sceneHorBigDepthBig, { timeStamp: performance.now() }) }) }) diff --git a/packages/pointer-events/package.json b/packages/pointer-events/package.json index 368caa5f..8e2a4e68 100644 --- a/packages/pointer-events/package.json +++ b/packages/pointer-events/package.json @@ -24,8 +24,9 @@ "main": "dist/index.js", "scripts": { "build": "tsc -p build.tsconfig.json", - "test": "vitest run", - "bench": "vitest bench", + "test": "vitest run --printConsoleTrace", + "bench-write": "vitest bench --output-json bench.json --run", + "bench-compare": "vitest bench --compare bench.json --run", "example": "vite example --host", "example:build": "vite build example" }, @@ -35,6 +36,6 @@ "@types/three": "^0.164.0", "three": "^0.164.1", "vite": "^5.2.11", - "vitest": "^1.6.0" + "vitest": "^2.0.5" } } diff --git a/packages/pointer-events/src/combine.ts b/packages/pointer-events/src/combine.ts index 4a8db30b..1404c4fd 100644 --- a/packages/pointer-events/src/combine.ts +++ b/packages/pointer-events/src/combine.ts @@ -1,19 +1,25 @@ import { Object3D } from 'three' import { NativeEvent } from './event.js' -import { Pointer } from './pointer.js' +import { Pointer, PointerCapture } from './pointer.js' +import { intersectPointerEventTargets } from './intersections/utils.js' +import { Intersection } from './index.js' export class CombinedPointer { - private pointers: Array = [] - private isDefaults: Array = [] + private readonly pointers: Array = [] + private readonly isDefaults: Array = [] private enabled: boolean = true + private activePointer: Pointer | CombinedPointer | undefined + private readonly nonCapturedPointers: Array = [] - register(pointer: Pointer, isDefault: boolean): () => void { + constructor(private readonly enableMultiplePointers: boolean) {} + + register(pointer: Pointer | CombinedPointer, isDefault: boolean = false): () => void { this.pointers.push(pointer) this.isDefaults.push(isDefault) return this.unregister.bind(this, pointer) } - private unregister(pointer: Pointer) { + private unregister(pointer: Pointer | CombinedPointer) { const index = this.pointers.indexOf(pointer) if (index === -1) { return @@ -22,44 +28,117 @@ export class CombinedPointer { this.pointers.splice(index, 1) } - move(scene: Object3D, nativeEvent: NativeEvent): void { - if (!this.enabled) { - return - } + private startIntersection(nonCapturedPointers: Array, nativeEvent: NativeEvent) { const length = this.pointers.length - - if (length === 0) { - return - } - for (let i = 0; i < length; i++) { - this.pointers[i].computeMove(scene, nativeEvent) + const pointer = this.pointers[i] + if (pointer instanceof CombinedPointer) { + pointer.startIntersection(nonCapturedPointers, nativeEvent) + continue + } + const pointerCapture = pointer.getPointerCapture() + if (pointerCapture != null) { + pointer.setIntersection(pointer.intersector.intersectPointerCapture(pointerCapture, nativeEvent)) + continue + } + nonCapturedPointers.push(pointer) + pointer.intersector.startIntersection(nativeEvent) } + } - let smallestIndex: number = 0 - let smallestDistance: number = this.pointers[0].getIntersection()?.distance ?? Infinity + /** + * only for internal use + */ + getIntersection(): Intersection | undefined { + return this.activePointer?.getIntersection() + } + + /** + * only for internal use + */ + getPointerCapture(): PointerCapture | undefined { + return this.activePointer?.getPointerCapture() + } - for (let i = 1; i < length; i++) { - const distance = this.pointers[i].getIntersection()?.distance ?? Infinity + private computeActivePointer() { + let smallestDistance: number | undefined + this.activePointer = undefined + const length = this.pointers.length + for (let i = 0; i < length; i++) { + const pointer = this.pointers[i] + if (pointer instanceof CombinedPointer) { + pointer.computeActivePointer() + } + const intersection = pointer.getIntersection() + const distance = pointer.getPointerCapture() != null ? -Infinity : (intersection?.distance ?? Infinity) const isDefault = this.isDefaults[i] - if ((isDefault && distance === smallestDistance) || distance < smallestDistance) { - smallestIndex = i + if (smallestDistance == null || (isDefault && distance === smallestDistance) || distance < smallestDistance) { + this.activePointer = pointer smallestDistance = distance } } + } + /** + * only for internal use + */ + commit(nativeEvent: NativeEvent, computeActivePointer: boolean = true): void { + if (this.enableMultiplePointers) { + const length = this.pointers.length + for (let i = 0; i < length; i++) { + this.pointers[i].commit(nativeEvent) + } + return + } + + if (computeActivePointer) { + this.computeActivePointer() + } + + //commit all pointers, enable the active pointer, and disable all other pointers + const length = this.pointers.length for (let i = 0; i < length; i++) { const pointer = this.pointers[i] - pointer.setEnabled(i === smallestIndex, nativeEvent, false) - pointer.commit(nativeEvent) + pointer.setEnabled(pointer === this.activePointer, nativeEvent, false) + pointer.commit(nativeEvent, false) + } + } + + move(scene: Object3D, nativeEvent: NativeEvent): void { + if (!this.enabled) { + return + } + + /* + slow version that stays in here for benchmarking + for (let i = 0; i < this.pointers.length; i++) { + this.pointers[i].move(scene, nativeEvent) + }*/ + + //start intersection, build nonCapturedPointers list, and compute the intersection for all captured pointers + this.nonCapturedPointers.length = 0 + this.startIntersection(this.nonCapturedPointers, nativeEvent) + + //intersect scene using the non captured pointers + intersectPointerEventTargets(scene, this.nonCapturedPointers) + + //finalize the intersection for the non captured pointers + const nonCapturedPointerLength = this.nonCapturedPointers.length + for (let i = 0; i < nonCapturedPointerLength; i++) { + const pointer = this.nonCapturedPointers[i] + pointer.setIntersection(pointer.intersector.finalizeIntersection()) } + + //commit the intersection, compute active pointers, and enabling/disabling pointers + this.commit(nativeEvent) } setEnabled(enabled: boolean, nativeEvent: NativeEvent): void { this.enabled = enabled const length = this.pointers.length for (let i = 0; i < length; i++) { - this.pointers[i].setEnabled(enabled, nativeEvent) + const pointer = this.pointers[i] + pointer.setEnabled(enabled && (this.enableMultiplePointers || pointer == this.activePointer), nativeEvent) } } } diff --git a/packages/pointer-events/src/forward.ts b/packages/pointer-events/src/forward.ts index 1a8960c7..95acb9c7 100644 --- a/packages/pointer-events/src/forward.ts +++ b/packages/pointer-events/src/forward.ts @@ -1,7 +1,7 @@ import { Camera, Object3D, Quaternion, Scene, Vector2, Vector3 } from 'three' -import { Pointer, PointerCapture, PointerOptions } from './pointer.js' +import { Pointer, PointerOptions } from './pointer.js' import { NativeEvent, NativeWheelEvent, PointerEvent } from './event.js' -import { intersectRayFromCamera } from './intersections/ray.js' +import { CameraRayIntersector } from './intersections/ray.js' import { generateUniquePointerId } from './pointer/index.js' import { IntersectionOptions } from './intersections/index.js' @@ -19,10 +19,6 @@ export type ForwardEventsOptions = { } & PointerOptions & IntersectionOptions -const vectorHelper = new Vector3() -const vector2Helper = new Vector2() -const quaternionHelper = new Quaternion() - function htmlEventToCoords(element: HTMLElement, e: unknown, target: Vector2): Vector2 { if (!(e instanceof globalThis.MouseEvent)) { return target.set(0, 0) @@ -94,7 +90,7 @@ function forwardEvents( }, toCamera: Camera, toScene: Object3D, - toCoords: (event: unknown, target: Vector2) => Vector2, + toCoords: (event: unknown, target: Vector2) => void, setPointerCapture: (pointerId: number) => void, releasePointerCapture: (ponterId: number) => void, options: ForwardEventsOptions = {}, @@ -107,27 +103,16 @@ function forwardEvents( if (innerPointer != null) { return innerPointer } - pointerType = `${pointerTypePrefix}${pointerType}` - const computeIntersection = (scene: Object3D, nativeEvent: unknown, pointerCapture: PointerCapture | undefined) => - intersectRayFromCamera( - toCamera, - toCoords(nativeEvent, vector2Helper), - toCamera.getWorldPosition(vectorHelper), - toCamera.getWorldQuaternion(quaternionHelper), - scene, - pointerId, - pointerType, - pointerState, - pointerCapture, - options, - ) pointerMap.set( pointerId, (innerPointer = new Pointer( generateUniquePointerId(), - pointerType, + `${pointerTypePrefix}${pointerType}`, pointerState, - computeIntersection, + new CameraRayIntersector((nativeEvent, coords) => { + toCoords(nativeEvent, coords) + return toCamera + }, options), undefined, forwardPointerCapture ? setPointerCapture.bind(null, pointerId) : undefined, forwardPointerCapture ? releasePointerCapture.bind(null, pointerId) : undefined, diff --git a/packages/pointer-events/src/intersections/index.ts b/packages/pointer-events/src/intersections/index.ts index 7aaca906..c06f8b95 100644 --- a/packages/pointer-events/src/intersections/index.ts +++ b/packages/pointer-events/src/intersections/index.ts @@ -1,4 +1,4 @@ -import { Intersection as ThreeIntersection, Quaternion, Vector3 } from 'three' +import { Intersection as ThreeIntersection, Quaternion, Vector3, Line3 } from 'three' export type Intersection = ThreeIntersection & { pointerPosition: Vector3 @@ -12,8 +12,8 @@ export type Intersection = ThreeIntersection & { details: | { type: 'lines' - lineIndex: number distanceOnLine: number + lineIndex: number } | { type: 'camera-ray' diff --git a/packages/pointer-events/src/intersections/intersector.ts b/packages/pointer-events/src/intersections/intersector.ts new file mode 100644 index 00000000..c79928db --- /dev/null +++ b/packages/pointer-events/src/intersections/intersector.ts @@ -0,0 +1,26 @@ +import { Object3D, Intersection as ThreeIntersection } from 'three' +import { Intersection } from '../index.js' +import { PointerCapture } from '../pointer.js' + +export abstract class Intersector { + //state of the current intersection + protected intersection: ThreeIntersection | undefined + protected pointerEventsOrder: number | undefined + + public startIntersection(nativeEvent: unknown): void { + this.intersection = undefined + this.pointerEventsOrder = undefined + this.prepareIntersection(nativeEvent) + } + + public abstract intersectPointerCapture( + pointerCapture: PointerCapture, + nativeEvent: unknown, + ): Intersection | undefined + + protected abstract prepareIntersection(nativeEvent: unknown): boolean + + public abstract executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void + + public abstract finalizeIntersection(): Intersection | undefined +} diff --git a/packages/pointer-events/src/intersections/lines.ts b/packages/pointer-events/src/intersections/lines.ts index 14894a70..7f7cedd8 100644 --- a/packages/pointer-events/src/intersections/lines.ts +++ b/packages/pointer-events/src/intersections/lines.ts @@ -9,140 +9,127 @@ import { Intersection as ThreeIntersection, Object3D, } from 'three' -import { Intersection, IntersectionOptions } from './index.js' -import { computeIntersectionWorldPlane, getDominantIntersectionIndex, traversePointerEventTargets } from './utils.js' +import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js' import type { PointerCapture } from '../pointer.js' +import { Intersector } from './intersector.js' +import { Intersection, IntersectionOptions } from '../index.js' -const raycaster = new Raycaster() const invertedMatrixHelper = new Matrix4() const intersectsHelper: Array = [] +const lineHelper = new Line3() +const planeHelper = new Plane() +const rayHelper = new Ray() +const defaultLinePoints = [new Vector3(0, 0, 0), new Vector3(0, 0, 1)] + +export class LinesIntersector extends Intersector { + private raycasters: Array = [] + private fromMatrixWorld = new Matrix4() + private intersectionLineIndex: number = 0 + private intersectionDistanceOnLine: number = 0 -export function intersectLines( - fromMatrixWorld: Matrix4, - linePoints: Array, - scene: Object3D, - pointerId: number, - pointerType: string, - pointerState: unknown, - pointerCapture: PointerCapture | undefined, - options: IntersectionOptions | undefined, -): Intersection | undefined { - if (pointerCapture != null) { - return intersectLinesPointerCapture(fromMatrixWorld, linePoints, pointerCapture) + constructor( + private readonly prepareTransformation: (nativeEvent: unknown, fromMatrixWorld: Matrix4) => boolean, + private readonly options: IntersectionOptions & { linePoints?: Array; minDistance?: number }, + ) { + super() } - let intersection: (ThreeIntersection & { details: { distanceOnLine: number; lineIndex: number } }) | undefined - let pointerEventsOrder: number | undefined - traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => { - let prevAccLineLength = 0 - const length = (intersection?.details.lineIndex ?? linePoints.length - 2) + 2 - for (let i = 1; i < length; i++) { - const start = linePoints[i - 1] - const end = linePoints[i] + public intersectPointerCapture( + { intersection, object }: PointerCapture, + nativeEvent: unknown, + ): Intersection | undefined { + const details = intersection.details + if (details.type != 'lines') { + return undefined + } + if (!this.prepareTransformation(nativeEvent, this.fromMatrixWorld)) { + return undefined + } + const linePoints = this.options.linePoints ?? defaultLinePoints + lineHelper.set(linePoints[details.lineIndex], linePoints[details.lineIndex + 1]).applyMatrix4(this.fromMatrixWorld) + + const point = lineHelper.at(details.distanceOnLine / lineHelper.distance(), new Vector3()) + computeIntersectionWorldPlane(planeHelper, intersection, object) + const pointOnFace = rayHelper.intersectPlane(planeHelper, new Vector3()) ?? point + return { + ...intersection, + pointOnFace, + point, + pointerPosition: new Vector3().setFromMatrixPosition(this.fromMatrixWorld), + pointerQuaternion: new Quaternion().setFromRotationMatrix(this.fromMatrixWorld), + } + } + + protected prepareIntersection(nativeEvent: unknown): boolean { + if (!this.prepareTransformation(nativeEvent, this.fromMatrixWorld)) { + return false + } + const linePoints = this.options.linePoints ?? defaultLinePoints + const length = linePoints.length - 1 + for (let i = 0; i < length; i++) { + const start = linePoints[i] + const end = linePoints[i + 1] + const raycaster = this.raycasters[i] ?? (this.raycasters[i] = new Raycaster()) //transform from local object to world - raycaster.ray.origin.copy(start).applyMatrix4(fromMatrixWorld) - raycaster.ray.direction.copy(end).applyMatrix4(fromMatrixWorld) + raycaster.ray.origin.copy(start).applyMatrix4(this.fromMatrixWorld) + raycaster.ray.direction.copy(end).applyMatrix4(this.fromMatrixWorld) //compute length & normalized direction raycaster.ray.direction.sub(raycaster.ray.origin) const lineLength = raycaster.ray.direction.length() raycaster.ray.direction.divideScalar(lineLength) - raycaster.far = lineLength - object.raycast(raycaster, intersectsHelper) + } + this.raycasters.length = length + return true + } - //we're adding the details and the prev acc line length so that the intersections are correctly sorted - const length = intersectsHelper.length - for (let intersectionIndex = 0; intersectionIndex < length; intersectionIndex++) { - const int = intersectsHelper[intersectionIndex] - const distanceOnLine = int.distance - int.distance += prevAccLineLength - Object.assign(int, { - details: { - lineIndex: i - 1, - distanceOnLine, - }, - }) + public executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void { + let lineLengthSum = 0 + const length = this.raycasters.length + //TODO: optimize - we only need to intersect with raycasters before or equal to the raycaster that did the current intersection + for (let i = 0; i < length; i++) { + const raycaster = this.raycasters[i] + object.raycast(raycaster, intersectsHelper) + for (const intersection of intersectsHelper) { + intersection.distance += lineLengthSum } const index = getDominantIntersectionIndex( - intersection, - pointerEventsOrder, + this.intersection, + this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, - options, + this.options, ) if (index != null) { - intersection = intersectsHelper[index] - pointerEventsOrder = objectPointerEventsOrder + this.intersection = intersectsHelper[index] + this.intersectionLineIndex = i + this.intersectionDistanceOnLine = this.intersection.distance - raycaster.far + this.pointerEventsOrder = objectPointerEventsOrder } intersectsHelper.length = 0 - prevAccLineLength += lineLength + lineLengthSum += raycaster.far } - }) - - if (intersection == null) { - return undefined } - return Object.assign(intersection, { - details: { - ...intersection.details, - type: 'lines' as const, - }, - pointerPosition: new Vector3().setFromMatrixPosition(fromMatrixWorld), - pointerQuaternion: new Quaternion().setFromRotationMatrix(fromMatrixWorld), - pointOnFace: intersection.point, - localPoint: intersection.point - .clone() - .applyMatrix4(invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()), - }) -} - -const lineHelper = new Line3() -const planeHelper = new Plane() - -function intersectLinesPointerCapture( - fromMatrixWorld: Matrix4, - linePoints: Array, - { intersection, object }: PointerCapture, -): Intersection | undefined { - const details = intersection.details - if (details.type != 'lines') { - return undefined - } - lineHelper.set(linePoints[details.lineIndex], linePoints[details.lineIndex + 1]).applyMatrix4(fromMatrixWorld) - - const point = lineHelper.at(details.distanceOnLine / lineHelper.distance(), new Vector3()) - computeIntersectionWorldPlane(planeHelper, intersection, object) - const pointOnFace = backwardsIntersectionLinesWithPlane(fromMatrixWorld, linePoints, planeHelper) ?? point - - return { - ...intersection, - pointOnFace, - point, - pointerPosition: new Vector3().setFromMatrixPosition(fromMatrixWorld), - pointerQuaternion: new Quaternion().setFromRotationMatrix(fromMatrixWorld), - } -} - -const vectorHelper = new Vector3() -const rayHelper = new Ray() - -function backwardsIntersectionLinesWithPlane( - fromMatrixWorld: Matrix4, - linePoints: Array, - plane: Plane, -): Vector3 | undefined { - for (let i = linePoints.length - 1; i > 0; i--) { - const start = linePoints[i - 1] - const end = linePoints[i] - rayHelper.origin.copy(start).applyMatrix4(fromMatrixWorld) - rayHelper.direction.copy(end).applyMatrix4(fromMatrixWorld).sub(raycaster.ray.origin).normalize() - const point = rayHelper.intersectPlane(plane, vectorHelper) - if (point != null) { - return vectorHelper.clone() + public finalizeIntersection(): Intersection | undefined { + if (this.intersection == null) { + return undefined } + //TODO: consider maxLength + return Object.assign(this.intersection, { + details: { + lineIndex: this.intersectionLineIndex, + distanceOnLine: this.intersectionDistanceOnLine, + type: 'lines' as const, + }, + pointerPosition: new Vector3().setFromMatrixPosition(this.fromMatrixWorld), + pointerQuaternion: new Quaternion().setFromRotationMatrix(this.fromMatrixWorld), + pointOnFace: this.intersection.point, + localPoint: this.intersection.point + .clone() + .applyMatrix4(invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert()), + }) } - return undefined } diff --git a/packages/pointer-events/src/intersections/ray.ts b/packages/pointer-events/src/intersections/ray.ts index 854abb21..092a9743 100644 --- a/packages/pointer-events/src/intersections/ray.ts +++ b/packages/pointer-events/src/intersections/ray.ts @@ -1,188 +1,201 @@ import { - Camera, Matrix4, Plane, Quaternion, Ray, Raycaster, - Vector2, Vector3, Intersection as ThreeIntersection, Object3D, + Camera, + Vector2, } from 'three' import { Intersection, IntersectionOptions } from './index.js' -import type { PointerCapture } from '../pointer.js' -import { computeIntersectionWorldPlane, getDominantIntersectionIndex, traversePointerEventTargets } from './utils.js' +import { Pointer, type PointerCapture } from '../pointer.js' +import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js' +import { Intersector } from './intersector.js' -const raycaster = new Raycaster() -const directionHelper = new Vector3() -const planeHelper = new Plane() const invertedMatrixHelper = new Matrix4() const intersectsHelper: Array = [] +const matrixHelper = new Matrix4() +const scaleHelper = new Vector3() +const NegZAxis = new Vector3(0, 0, -1) +const directionHelper = new Vector3() +const planeHelper = new Plane() + +export class RayIntersector extends Intersector { + private readonly raycaster = new Raycaster() + private readonly raycasterQuaternion = new Quaternion() + private worldScale: number = 0 + + constructor( + private readonly prepareTransformation: (nativeEvent: unknown, matrixWorld: Matrix4) => boolean, + private readonly options: IntersectionOptions & { minDistance?: number; direction?: Vector3 }, + ) { + super() + } -export function intersectRay( - fromPosition: Vector3, - fromQuaternion: Quaternion, - direction: Vector3, - scene: Object3D, - pointerId: number, - pointerType: string, - pointerState: unknown, - pointerCapture: PointerCapture | undefined, - options: IntersectionOptions | undefined, -): Intersection | undefined { - if (pointerCapture != null) { - return intersectRayPointerCapture(fromPosition, fromQuaternion, direction, pointerCapture) + public intersectPointerCapture( + { intersection, object }: PointerCapture, + nativeEvent: unknown, + ): Intersection | undefined { + if (intersection.details.type != 'ray') { + return undefined + } + if (!this.prepareIntersection(nativeEvent)) { + return undefined + } + computeIntersectionWorldPlane(planeHelper, intersection, object) + const { ray } = this.raycaster + const pointOnFace = ray.intersectPlane(planeHelper, new Vector3()) ?? intersection.point + return { + ...intersection, + object, + pointOnFace, + point: ray.direction.clone().multiplyScalar(intersection.distance).add(ray.origin), + pointerPosition: ray.origin.clone(), + pointerQuaternion: this.raycasterQuaternion.clone(), + } + } + + protected prepareIntersection(nativeEvent: unknown): boolean { + if (!this.prepareTransformation(nativeEvent, matrixHelper)) { + return false + } + matrixHelper.decompose(this.raycaster.ray.origin, this.raycasterQuaternion, scaleHelper) + this.worldScale = scaleHelper.x + this.raycaster.ray.direction.copy(this.options?.direction ?? NegZAxis).applyQuaternion(this.raycasterQuaternion) + return true } - let intersection: (ThreeIntersection & { pointerEventsOrder?: number }) | undefined - let pointerEventsOrder: number | undefined - raycaster.ray.origin.copy(fromPosition) - raycaster.ray.direction.copy(direction).applyQuaternion(fromQuaternion) - traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => { - object.raycast(raycaster, intersectsHelper) + + public executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void { + object.raycast(this.raycaster, intersectsHelper) const index = getDominantIntersectionIndex( - intersection, - pointerEventsOrder, + this.intersection, + this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, - options, + this.options, ) if (index != null) { - intersection = intersectsHelper[index] - pointerEventsOrder = objectPointerEventsOrder + this.intersection = intersectsHelper[index] + this.pointerEventsOrder = objectPointerEventsOrder } intersectsHelper.length = 0 - }) - if (intersection == null) { - return undefined } - return Object.assign(intersection, { - details: { - type: 'ray' as const, - }, - pointerPosition: fromPosition.clone(), - pointerQuaternion: fromQuaternion.clone(), - pointOnFace: intersection.point, - localPoint: intersection.point - .clone() - .applyMatrix4(invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()), - }) -} - -const rayHelper = new Ray() -function intersectRayPointerCapture( - fromPosition: Vector3, - fromQuaternion: Quaternion, - direction: Vector3, - { intersection, object }: PointerCapture, -): Intersection | undefined { - if (intersection.details.type != 'ray') { - return undefined - } - directionHelper.copy(direction).applyQuaternion(fromQuaternion) - rayHelper.set(fromPosition, directionHelper) - computeIntersectionWorldPlane(planeHelper, intersection, object) - const pointOnFace = rayHelper.intersectPlane(planeHelper, new Vector3()) ?? intersection.point - return { - ...intersection, - object, - pointOnFace, - point: directionHelper.clone().multiplyScalar(intersection.distance).add(fromPosition), - pointerPosition: fromPosition.clone(), - pointerQuaternion: fromQuaternion.clone(), + public finalizeIntersection(): Intersection | undefined { + if (this.intersection == null) { + return undefined + } + if (this.options.minDistance != null && this.intersection.distance * this.worldScale < this.options.minDistance) { + return undefined + } + return Object.assign(this.intersection, { + details: { + type: 'ray' as const, + }, + pointerPosition: this.raycaster.ray.origin.clone(), + pointerQuaternion: this.raycasterQuaternion.clone(), + pointOnFace: this.intersection.point, + localPoint: this.intersection.point + .clone() + .applyMatrix4(invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert()), + }) } } -export function intersectRayFromCamera( - from: Camera, - coords: Vector2, - fromPosition: Vector3, - fromQuaternion: Quaternion, - scene: Object3D, - pointerId: number, - pointerType: string, - pointerState: unknown, - pointerCapture: PointerCapture | undefined, - options: IntersectionOptions | undefined, -): Intersection | undefined { - if (pointerCapture != null) { - return intersectRayFromCameraPointerCapture(from, coords, fromPosition, fromQuaternion, pointerCapture) +export class CameraRayIntersector extends Intersector { + private readonly raycaster = new Raycaster() + private readonly fromPosition = new Vector3() + private readonly fromQuaternion = new Quaternion() + private readonly coords = new Vector2() + + private viewPlane = new Plane() + + constructor( + private readonly prepareTransformation: (nativeEvent: unknown, coords: Vector2) => Camera | undefined, + private readonly options: IntersectionOptions, + ) { + super() } - let intersection: ThreeIntersection | undefined - let pointerEventsOrder: number | undefined - raycaster.setFromCamera(coords, from) + public intersectPointerCapture( + { intersection, object }: PointerCapture, + nativeEvent: unknown, + ): Intersection | undefined { + const details = intersection.details + if (details.type != 'camera-ray') { + return undefined + } + if (!this.prepareIntersection(nativeEvent)) { + return undefined + } + this.viewPlane.constant -= details.distanceViewPlane + + //find captured intersection point by intersecting the ray to the plane of the camera + const point = this.raycaster.ray.intersectPlane(this.viewPlane, new Vector3()) + + if (point == null) { + return undefined + } + + computeIntersectionWorldPlane(this.viewPlane, intersection, object) + const pointOnFace = this.raycaster.ray.intersectPlane(this.viewPlane, new Vector3()) ?? point + return { + ...intersection, + object, + point, + pointOnFace, + pointerPosition: this.fromPosition.clone(), + pointerQuaternion: this.fromQuaternion.clone(), + } + } - planeHelper.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), raycaster.ray.origin) + protected prepareIntersection(nativeEvent: unknown): boolean { + const from = this.prepareTransformation(nativeEvent, this.coords) + if (from == null) { + return false + } + from.matrixWorld.decompose(this.fromPosition, this.fromQuaternion, scaleHelper) + from.updateWorldMatrix(true, false) + this.raycaster.setFromCamera(this.coords, from) + this.viewPlane.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), this.raycaster.ray.origin) + return true + } - traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => { - object.raycast(raycaster, intersectsHelper) + public executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void { + object.raycast(this.raycaster, intersectsHelper) const index = getDominantIntersectionIndex( - intersection, - pointerEventsOrder, + this.intersection, + this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, - options, + this.options, ) if (index != null) { - intersection = intersectsHelper[index] - pointerEventsOrder = objectPointerEventsOrder + this.intersection = intersectsHelper[index] + this.pointerEventsOrder = objectPointerEventsOrder } intersectsHelper.length = 0 - }) - - if (intersection == null) { - return undefined } - invertedMatrixHelper.copy(intersection.object.matrixWorld).invert() - - return Object.assign(intersection, { - details: { - type: 'camera-ray' as const, - distanceViewPlane: planeHelper.distanceToPoint(intersection.point), - }, - pointOnFace: intersection.point, - pointerPosition: fromPosition.clone(), - pointerQuaternion: fromQuaternion.clone(), - localPoint: intersection.point.clone().applyMatrix4(invertedMatrixHelper), - }) -} - -function intersectRayFromCameraPointerCapture( - from: Camera, - coords: Vector2, - fromPosition: Vector3, - fromQuaternion: Quaternion, - { intersection, object }: PointerCapture, -): Intersection | undefined { - const details = intersection.details - if (details.type != 'camera-ray') { - return undefined - } - raycaster.setFromCamera(coords, from) - - from.getWorldDirection(directionHelper) - //set the plane to the viewPlane + the distance of the prev intersection in the camera distance - planeHelper.setFromNormalAndCoplanarPoint(directionHelper, raycaster.ray.origin) - planeHelper.constant -= details.distanceViewPlane - - //find captured intersection point by intersecting the ray to the plane of the camera - const point = raycaster.ray.intersectPlane(planeHelper, new Vector3()) - - if (point == null) { - return undefined - } + public finalizeIntersection(): Intersection | undefined { + if (this.intersection == null) { + return undefined + } - computeIntersectionWorldPlane(planeHelper, intersection, object) - const pointOnFace = raycaster.ray.intersectPlane(planeHelper, new Vector3()) ?? point - return { - ...intersection, - object, - point, - pointOnFace, - pointerPosition: fromPosition.clone(), - pointerQuaternion: fromQuaternion.clone(), + invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert() + + return Object.assign(this.intersection, { + details: { + type: 'camera-ray' as const, + distanceViewPlane: this.viewPlane.distanceToPoint(this.intersection.point), + }, + pointOnFace: this.intersection.point, + pointerPosition: this.fromPosition.clone(), + pointerQuaternion: this.fromQuaternion.clone(), + localPoint: this.intersection.point.clone().applyMatrix4(invertedMatrixHelper), + }) } } diff --git a/packages/pointer-events/src/intersections/sphere.ts b/packages/pointer-events/src/intersections/sphere.ts index f3d9f894..489c0499 100644 --- a/packages/pointer-events/src/intersections/sphere.ts +++ b/packages/pointer-events/src/intersections/sphere.ts @@ -9,63 +9,111 @@ import { Intersection as ThreeIntersection, Plane, } from 'three' -import { Intersection, IntersectionOptions } from './index.js' -import { computeIntersectionWorldPlane, getDominantIntersectionIndex, traversePointerEventTargets } from './utils.js' +import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js' import type { PointerCapture } from '../pointer.js' +import { Intersector } from './intersector.js' +import { Intersection, IntersectionOptions } from '../index.js' const collisionSphere = new Sphere() const intersectsHelper: Array = [] -export function intersectSphere( - fromPosition: Vector3, - fromQuaternion: Quaternion, - radius: number, - scene: Object3D, - pointerId: number, - pointerType: string, - pointerState: unknown, - pointerCapture: PointerCapture | undefined, - options: IntersectionOptions | undefined, -): Intersection | undefined { - if (pointerCapture != null) { - return intersectSpherePointerCapture(fromPosition, fromQuaternion, pointerCapture) - } - let intersection: ThreeIntersection | undefined - let pointerEventsOrder: number | undefined - collisionSphere.center.copy(fromPosition) - collisionSphere.radius = radius - - traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => { +export class SphereIntersector extends Intersector { + private readonly fromPosition = new Vector3() + private readonly fromQuaternion = new Quaternion() + + constructor( + /** + * @returns the sphere radius + */ + private readonly prepareTransformation: ( + nativeEvent: unknown, + fromPosition: Vector3, + fromQuaternion: Quaternion, + ) => number | undefined, + private readonly options: IntersectionOptions, + ) { + super() + } + + public intersectPointerCapture( + { intersection, object }: PointerCapture, + nativeEvent: unknown, + ): Intersection | undefined { + if (intersection.details.type != 'sphere') { + return undefined + } + if (this.prepareTransformation(nativeEvent, this.fromPosition, this.fromQuaternion) == null) { + return undefined + } + //compute old inputDevicePosition-point offset + oldInputDevicePointOffset.copy(intersection.point).sub(intersection.pointerPosition) + //compute oldInputDeviceQuaternion-newInputDeviceQuaternion offset + inputDeviceQuaternionOffset.copy(intersection.pointerQuaternion).invert().multiply(this.fromQuaternion) + //apply quaternion offset to old inputDevicePosition-point offset and add to new inputDevicePosition + const point = oldInputDevicePointOffset.clone().applyQuaternion(inputDeviceQuaternionOffset).add(this.fromPosition) + + computeIntersectionWorldPlane(planeHelper, intersection, object) + + const pointOnFace = planeHelper.projectPoint(this.fromPosition, new Vector3()) + + return { + details: { + type: 'sphere', + }, + distance: intersection.distance, + pointerPosition: this.fromPosition.clone(), + pointerQuaternion: this.fromQuaternion.clone(), + object, + point, + pointOnFace, + face: intersection.face, + localPoint: intersection.localPoint, + } + } + + protected prepareIntersection(nativeEvent: unknown): boolean { + const radius = this.prepareTransformation(nativeEvent, this.fromPosition, this.fromQuaternion) + if (radius == null) { + return false + } + collisionSphere.center.copy(this.fromPosition) + collisionSphere.radius = radius + return true + } + + public executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void { intersectSphereWithObject(collisionSphere, object, intersectsHelper) const index = getDominantIntersectionIndex( - intersection, - pointerEventsOrder, + this.intersection, + this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, - options, + this.options, ) if (index != null) { - intersection = intersectsHelper[index] - pointerEventsOrder = objectPointerEventsOrder + this.intersection = intersectsHelper[index] + this.pointerEventsOrder = objectPointerEventsOrder } intersectsHelper.length = 0 - }) - - if (intersection == null) { - return undefined } - return Object.assign(intersection, { - details: { - type: 'sphere' as const, - }, - pointOnFace: intersection.point, - pointerPosition: fromPosition.clone(), - pointerQuaternion: fromQuaternion.clone(), - localPoint: intersection.point - .clone() - .applyMatrix4(invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()), - }) + public finalizeIntersection(): Intersection | undefined { + if (this.intersection == null) { + return undefined + } + + return Object.assign(this.intersection, { + details: { + type: 'sphere' as const, + }, + pointOnFace: this.intersection.point, + pointerPosition: this.fromPosition.clone(), + pointerQuaternion: this.fromQuaternion.clone(), + localPoint: this.intersection.point + .clone() + .applyMatrix4(invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert()), + }) + } } const matrixHelper = new Matrix4() @@ -124,40 +172,6 @@ const oldInputDevicePointOffset = new Vector3() const inputDeviceQuaternionOffset = new Quaternion() const planeHelper = new Plane() -function intersectSpherePointerCapture( - fromPosition: Vector3, - fromQuaterion: Quaternion, - { intersection, object }: PointerCapture, -): Intersection | undefined { - if (intersection.details.type != 'sphere') { - return undefined - } - //compute old inputDevicePosition-point offset - oldInputDevicePointOffset.copy(intersection.point).sub(intersection.pointerPosition) - //compute oldInputDeviceQuaternion-newInputDeviceQuaternion offset - inputDeviceQuaternionOffset.copy(intersection.pointerQuaternion).invert().multiply(fromQuaterion) - //apply quaternion offset to old inputDevicePosition-point offset and add to new inputDevicePosition - const point = oldInputDevicePointOffset.clone().applyQuaternion(inputDeviceQuaternionOffset).add(fromPosition) - - computeIntersectionWorldPlane(planeHelper, intersection, object) - - const pointOnFace = planeHelper.projectPoint(fromPosition, new Vector3()) - - return { - details: { - type: 'sphere', - }, - distance: intersection.distance, - pointerPosition: fromPosition.clone(), - pointerQuaternion: fromQuaterion.clone(), - object, - point, - pointOnFace, - face: intersection.face, - localPoint: intersection.localPoint, - } -} - const helperSphere = new Sphere() function isSphereIntersectingMesh(pointerSphere: Sphere, { geometry }: Mesh, meshMatrixWorld: Matrix4): boolean { diff --git a/packages/pointer-events/src/intersections/utils.ts b/packages/pointer-events/src/intersections/utils.ts index 7f46d498..e355ebde 100644 --- a/packages/pointer-events/src/intersections/utils.ts +++ b/packages/pointer-events/src/intersections/utils.ts @@ -1,7 +1,8 @@ import { Plane, Intersection as ThreeIntersection, Object3D } from 'three' import { Intersection, IntersectionOptions } from './index.js' -import { AllowedPointerEventsType, type AllowedPointerEvents } from '../pointer.js' +import { AllowedPointerEventsType, Pointer, type AllowedPointerEvents } from '../pointer.js' import { hasObjectListeners } from '../utils.js' +import { CombinedPointer } from '../index.js' export function computeIntersectionWorldPlane(target: Plane, intersection: Intersection, object: Object3D): boolean { if (intersection.face == null) { @@ -16,10 +17,7 @@ function isPointerEventsAllowed( hasListener: boolean, pointerEvents: AllowedPointerEvents, pointerEventsType: AllowedPointerEventsType, - pointerId: number, - pointerType: string, - pointerState: unknown, -): boolean { +): boolean | ((pointer: Pointer) => boolean) { if (pointerEvents === 'none') { return false } @@ -30,7 +28,7 @@ function isPointerEventsAllowed( return true } if (typeof pointerEventsType === 'function') { - return pointerEventsType(pointerId, pointerType, pointerState) + return ({ id, type, state }) => pointerEventsType(id, type, state) } let value: Array | string let invert: boolean @@ -41,53 +39,50 @@ function isPointerEventsAllowed( invert = false value = pointerEventsType.allow } - let result: boolean if (Array.isArray(value)) { - result = value.includes(pointerType) - } else { - result = value === pointerType + return (pointer) => invertIf(value.includes(pointer.type), invert) } + return (pointer) => invertIf(value === pointer.type, invert) +} - return invert ? !result : result +function invertIf(toInvert: boolean, ifIsTrue: boolean): boolean { + return ifIsTrue ? !toInvert : toInvert } -export function traversePointerEventTargets( +export function intersectPointerEventTargets( object: Object3D, - pointerId: number, - pointerType: string, - pointerState: unknown, - callback: (object: Object3D, pointerEventsOrder: number | undefined) => void, + pointers: Array, parentHasListener: boolean = false, parentPointerEvents?: AllowedPointerEvents, parentPointerEventsType?: AllowedPointerEventsType, parentPointerEventsOrder?: number, ): void { const hasListener = parentHasListener || hasObjectListeners(object) - const pointerEvents = object.pointerEvents ?? parentPointerEvents - const pointerEventsType = object.pointerEventsType ?? parentPointerEventsType + const pointerEvents = object.pointerEvents ?? parentPointerEvents ?? 'listener' + const pointerEventsType = object.pointerEventsType ?? parentPointerEventsType ?? 'all' const pointerEventsOrder = object.pointerEventsOrder ?? parentPointerEventsOrder - const isAllowed = isPointerEventsAllowed( - hasListener, - pointerEvents ?? 'listener', - pointerEventsType ?? 'all', - pointerId, - pointerType, - pointerState, - ) - - if (isAllowed) { - callback(object, pointerEventsOrder) + const isAllowed = isPointerEventsAllowed(hasListener, pointerEvents, pointerEventsType) + const length = pointers.length + if (isAllowed === true) { + for (let i = 0; i < length; i++) { + pointers[i].intersector.executeIntersection(object, pointerEventsOrder) + } + } else if (typeof isAllowed === 'function') { + for (let i = 0; i < length; i++) { + const pointer = pointers[i] + if (!isAllowed(pointer)) { + continue + } + pointers[i].intersector.executeIntersection(object, pointerEventsOrder) + } } - const length = object.children.length - for (let i = 0; i < length; i++) { - traversePointerEventTargets( + const childrenLength = object.children.length + for (let i = 0; i < childrenLength; i++) { + intersectPointerEventTargets( object.children[i], - pointerId, - pointerType, - pointerState, - callback, + pointers, hasListener, pointerEvents, pointerEventsType, @@ -98,6 +93,7 @@ export function traversePointerEventTargets( /** * @returns undefined if `i1` is the dominant intersection + * @param i2DistanceOffset modifies i2 and adds the i2DistanceOffset to the current distance */ export function getDominantIntersectionIndex( i1: T | undefined, diff --git a/packages/pointer-events/src/pointer.ts b/packages/pointer-events/src/pointer.ts index 6336f65f..b86bea1d 100644 --- a/packages/pointer-events/src/pointer.ts +++ b/packages/pointer-events/src/pointer.ts @@ -1,6 +1,8 @@ -import { Object3D } from 'three' +import { Object3D, Intersection as ThreeIntersection } from 'three' import { Intersection } from './intersections/index.js' import { NativeEvent, NativeWheelEvent, PointerEvent, WheelEvent, emitPointerEvent } from './event.js' +import { intersectPointerEventTargets } from './intersections/utils.js' +import { Intersector } from './intersections/intersector.js' const buttonsDownTimeKey = Symbol('buttonsDownTime') const buttonsClickTimeKey = Symbol('buttonsClickTime') @@ -112,11 +114,7 @@ export class Pointer { public readonly id: number, public readonly type: string, public readonly state: any, - private readonly computeIntersection: ( - scene: Object3D, - nativeEvent: unknown, - pointerCapture: PointerCapture | undefined, - ) => Intersection | undefined, + public readonly intersector: Intersector, private readonly onMoveCommited?: (pointer: Pointer) => void, private readonly parentSetPointerCapture?: () => void, private readonly parentReleasePointerCapture?: () => void, @@ -125,6 +123,10 @@ export class Pointer { pointerMap.set(id, this) } + getPointerCapture(): PointerCapture | undefined { + return this.pointerCapture + } + hasCaptured(object: Object3D): boolean { return this.pointerCapture?.object === object } @@ -171,13 +173,26 @@ export class Pointer { } } + private computeIntersection(scene: Object3D, nativeEvent: NativeEvent) { + if (this.pointerCapture != null) { + return this.intersector.intersectPointerCapture(this.pointerCapture, nativeEvent) + } + this.intersector.startIntersection(nativeEvent) + intersectPointerEventTargets(scene, [this]) + return this.intersector.finalizeIntersection() + } + + setIntersection(intersection: Intersection | undefined): void { + this.intersection = intersection + } + /** * allows to separately compute and afterwards commit a move * => do not forget to call commitMove after computeMove * can be used to compute the current intersection and disable or enable the pointer before commiting the move */ computeMove(scene: Object3D, nativeEvent: NativeEvent) { - this.intersection = this.computeIntersection(scene, nativeEvent, this.pointerCapture) + this.intersection = this.computeIntersection(scene, nativeEvent) } commit(nativeEvent: NativeEvent) { @@ -336,7 +351,7 @@ export class Pointer { } let intersection = this.intersection if (!useCurrentIntersection) { - intersection = this.computeIntersection(scene, nativeEvent, this.pointerCapture) + intersection = this.computeIntersection(scene, nativeEvent) } if (!this.wasMoved && useCurrentIntersection) { this.onFirstMove.push(this.cancel.bind(this, nativeEvent)) diff --git a/packages/pointer-events/src/pointer/grab.ts b/packages/pointer-events/src/pointer/grab.ts index d6d1b9cb..ec3237f6 100644 --- a/packages/pointer-events/src/pointer/grab.ts +++ b/packages/pointer-events/src/pointer/grab.ts @@ -1,6 +1,6 @@ -import { Object3D, Quaternion, Vector3 } from 'three' +import { Object3D } from 'three' import { Pointer, PointerOptions } from '../pointer.js' -import { intersectSphere } from '../intersections/sphere.js' +import { SphereIntersector } from '../intersections/sphere.js' import { generateUniquePointerId } from './index.js' import { IntersectionOptions } from '../intersections/index.js' @@ -12,24 +12,17 @@ export type GrabPointerOptions = { } & PointerOptions & IntersectionOptions -export const defaultGrabPointerOptions = { - radius: 0.07, -} satisfies GrabPointerOptions - export function createGrabPointer( space: { current?: Object3D | null }, pointerState: any, - options: GrabPointerOptions = defaultGrabPointerOptions, + options: GrabPointerOptions = {}, pointerType: string = 'grab', ) { - const fromPosition = new Vector3() - const fromQuaternion = new Quaternion() - const poinerId = generateUniquePointerId() return new Pointer( - poinerId, + generateUniquePointerId(), pointerType, pointerState, - (scene, _, pointerCapture) => { + new SphereIntersector((_nativeEvent, fromPosition, fromQuaternion) => { const spaceObject = space.current if (spaceObject == null) { return undefined @@ -37,18 +30,8 @@ export function createGrabPointer( spaceObject.updateWorldMatrix(true, false) fromPosition.setFromMatrixPosition(spaceObject.matrixWorld) fromQuaternion.setFromRotationMatrix(spaceObject.matrixWorld) - return intersectSphere( - fromPosition, - fromQuaternion, - options.radius ?? defaultGrabPointerOptions.radius, - scene, - poinerId, - pointerType, - pointerState, - pointerCapture, - options, - ) - }, + return options.radius ?? 0.07 + }, options), undefined, undefined, undefined, diff --git a/packages/pointer-events/src/pointer/index.ts b/packages/pointer-events/src/pointer/index.ts index a130dcba..269eb931 100644 --- a/packages/pointer-events/src/pointer/index.ts +++ b/packages/pointer-events/src/pointer/index.ts @@ -6,4 +6,5 @@ export function generateUniquePointerId() { export * from './grab.js' export * from './ray.js' +export * from './lines.js' export * from './touch.js' diff --git a/packages/pointer-events/src/pointer/lines.ts b/packages/pointer-events/src/pointer/lines.ts new file mode 100644 index 00000000..1ec0d097 --- /dev/null +++ b/packages/pointer-events/src/pointer/lines.ts @@ -0,0 +1,43 @@ +import { Object3D, Vector3 } from 'three' +import { Pointer, PointerOptions } from '../pointer.js' +import { IntersectionOptions, LinesIntersector } from '../intersections/index.js' +import { generateUniquePointerId } from './index.js' + +export type LinesPointerOptions = { + /** + * @default 0 + * distance to intersection in local space + */ + minDistance?: number + /** + * points for that compose the lines + * @default [new Vector3(0,0,0), new Vector3(0,0,1)] + */ + linePoints?: Array +} & PointerOptions & + IntersectionOptions + +export function createLinesPointer( + space: { current?: Object3D | null }, + pointerState: any, + options: LinesPointerOptions = {}, + pointerType: string = 'lines', +) { + return new Pointer( + generateUniquePointerId(), + pointerType, + pointerState, + new LinesIntersector((_nativeEvent, fromMatrixWorld) => { + const spaceObject = space.current + if (spaceObject == null) { + return false + } + fromMatrixWorld.copy(spaceObject.matrixWorld) + return true + }, options), + undefined, + undefined, + undefined, + options, + ) +} diff --git a/packages/pointer-events/src/pointer/ray.ts b/packages/pointer-events/src/pointer/ray.ts index 066a8109..6bb44d83 100644 --- a/packages/pointer-events/src/pointer/ray.ts +++ b/packages/pointer-events/src/pointer/ray.ts @@ -1,6 +1,6 @@ -import { Object3D, Quaternion, Vector3 } from 'three' +import { Object3D, Vector3 } from 'three' import { Pointer, PointerOptions } from '../pointer.js' -import { Intersection, IntersectionOptions, intersectLines, intersectRay } from '../intersections/index.js' +import { IntersectionOptions, RayIntersector } from '../intersections/index.js' import { generateUniquePointerId } from './index.js' export type RayPointerOptions = { @@ -9,10 +9,6 @@ export type RayPointerOptions = { * distance to intersection in local space */ minDistance?: number - /** - * @default null - */ - linePoints?: Array | null /** * @default NegZAxis */ @@ -20,71 +16,23 @@ export type RayPointerOptions = { } & PointerOptions & IntersectionOptions -const NegZAxis = new Vector3(0, 0, -1) -const vectorHelper = new Vector3() - -export const defaultRayPointerOptions = { - direction: NegZAxis, - minDistance: 0, - linePoints: null, -} satisfies RayPointerOptions - export function createRayPointer( space: { current?: Object3D | null }, pointerState: any, - options: RayPointerOptions = defaultRayPointerOptions, + options: RayPointerOptions = {}, pointerType: string = 'ray', ) { - const fromPosition = new Vector3() - const fromQuaternion = new Quaternion() - const pointerId = generateUniquePointerId() return new Pointer( - pointerId, + generateUniquePointerId(), pointerType, pointerState, - (scene, _, pointerCapture) => { - const spaceObject = space.current - if (spaceObject == null) { - return undefined - } - spaceObject.updateWorldMatrix(true, false) - let intersection: Intersection | undefined - const linePoints = options.linePoints ?? defaultRayPointerOptions.linePoints - if (linePoints == null) { - fromPosition.setFromMatrixPosition(spaceObject.matrixWorld) - fromQuaternion.setFromRotationMatrix(spaceObject.matrixWorld) - intersection = intersectRay( - fromPosition, - fromQuaternion, - options.direction ?? defaultRayPointerOptions.direction, - scene, - pointerId, - pointerType, - pointerState, - pointerCapture, - options, - ) - } else { - intersection = intersectLines( - spaceObject.matrixWorld, - linePoints, - scene, - pointerId, - pointerType, - pointerState, - pointerCapture, - options, - ) - } - if (intersection == null) { - return undefined - } - const localDistance = intersection.distance * spaceObject.getWorldScale(vectorHelper).x - if (localDistance < (options.minDistance ?? defaultRayPointerOptions.minDistance)) { - return undefined + new RayIntersector((_nativeEvent, matrixWorld) => { + if (space.current == null) { + return false } - return intersection - }, + matrixWorld.copy(space.current.matrixWorld) + return true + }, options), undefined, undefined, undefined, diff --git a/packages/pointer-events/src/pointer/touch.ts b/packages/pointer-events/src/pointer/touch.ts index 51d8f83b..6289e7d1 100644 --- a/packages/pointer-events/src/pointer/touch.ts +++ b/packages/pointer-events/src/pointer/touch.ts @@ -1,6 +1,6 @@ -import { Object3D, Quaternion, Scene, Vector3 } from 'three' +import { Object3D } from 'three' import { Pointer, PointerOptions } from '../pointer.js' -import { Intersection, IntersectionOptions, intersectSphere } from '../intersections/index.js' +import { Intersection, IntersectionOptions, SphereIntersector } from '../intersections/index.js' import { generateUniquePointerId } from './index.js' export type TouchPointerOptions = { @@ -19,26 +19,17 @@ export type TouchPointerOptions = { } & PointerOptions & IntersectionOptions -export const defaultTouchPointerOptions = { - button: 0, - downRadius: 0.03, - hoverRadius: 0.1, -} satisfies TouchPointerOptions - export function createTouchPointer( space: { current?: Object3D | null }, pointerState: any, - options: TouchPointerOptions = defaultTouchPointerOptions, + options: TouchPointerOptions = {}, pointerType: string = 'touch', ) { - const fromPosition = new Vector3() - const fromQuaternion = new Quaternion() - const pointerId = generateUniquePointerId() return new Pointer( - pointerId, + generateUniquePointerId(), pointerType, pointerState, - (scene, _, pointerCapture) => { + new SphereIntersector((_nativeEvent, fromPosition, fromQuaternion) => { const spaceObject = space.current if (spaceObject == null) { return undefined @@ -46,18 +37,8 @@ export function createTouchPointer( spaceObject.updateWorldMatrix(true, false) fromPosition.setFromMatrixPosition(spaceObject.matrixWorld) fromQuaternion.setFromRotationMatrix(spaceObject.matrixWorld) - return intersectSphere( - fromPosition, - fromQuaternion, - options.hoverRadius ?? defaultTouchPointerOptions.hoverRadius, - scene, - pointerId, - pointerType, - pointerState, - pointerCapture, - options, - ) - }, + return options.hoverRadius ?? 0.1 + }, options), createUpdateTouchPointer(options), undefined, undefined, @@ -65,21 +46,18 @@ export function createTouchPointer( ) } -function createUpdateTouchPointer(options: TouchPointerOptions = defaultTouchPointerOptions) { +function createUpdateTouchPointer(options: TouchPointerOptions) { let wasPointerDown = false return (pointer: Pointer) => { if (!pointer.getEnabled()) { return } const intersection = pointer.getIntersection() - const isPointerDown = computeIsPointerDown( - intersection, - options.downRadius ?? defaultTouchPointerOptions.downRadius, - ) + const isPointerDown = computeIsPointerDown(intersection, options.downRadius ?? 0.03) if (isPointerDown === wasPointerDown) { return } - const nativeEvent = { timeStamp: performance.now(), button: options.button ?? defaultTouchPointerOptions.button } + const nativeEvent = { timeStamp: performance.now(), button: options.button ?? 0 } if (isPointerDown) { pointer.down(nativeEvent) } else { diff --git a/packages/react/xr/src/default.tsx b/packages/react/xr/src/default.tsx index 5c0ae884..7cd01b11 100644 --- a/packages/react/xr/src/default.tsx +++ b/packages/react/xr/src/default.tsx @@ -24,6 +24,7 @@ import { PointerCursorModel, PointerRayModel, useGrabPointer, + useLinesPointer, usePointerXRInputSourceEvents, useRayPointer, useTouchPointer, @@ -316,7 +317,7 @@ export function DefaultXRInputSourceTeleportPointer(options: DefaultXRInputSourc const ref = useRef(null) const groupRef = useRef(null) const linePoints = useMemo(() => createTeleportRayLine(), []) - const pointer = useRayPointer( + const pointer = useLinesPointer( groupRef, state, { diff --git a/packages/react/xr/src/pointer.tsx b/packages/react/xr/src/pointer.tsx index c48068fa..ed3414a0 100644 --- a/packages/react/xr/src/pointer.tsx +++ b/packages/react/xr/src/pointer.tsx @@ -2,15 +2,14 @@ import { ReactNode, RefObject, forwardRef, useContext, useEffect, useImperativeH import { CombinedPointer as CombinedPointerImpl, GrabPointerOptions, + LinesPointerOptions, Pointer, RayPointerOptions, TouchPointerOptions, createGrabPointer, + createLinesPointer, createRayPointer, createTouchPointer, - defaultGrabPointerOptions, - defaultRayPointerOptions, - defaultTouchPointerOptions, } from '@pmndrs/pointer-events' import { Mesh, Object3D } from 'three' import { createPortal, useFrame, useThree } from '@react-three/fiber' @@ -23,8 +22,7 @@ import { updatePointerCursorModel, updatePointerRayModel, } from '@pmndrs/xr/internals' -import { useXR, useXRStore } from './xr.js' -import { setupSyncIsVisible } from '@pmndrs/xr' +import { useXR } from './xr.js' import { combinedPointerContext } from './contexts.js' //for checking if `event.pointerState` is from an xr input source @@ -34,12 +32,17 @@ export { type XRInputSourceState, isXRInputSourceState } from '@pmndrs/xr/intern * component for combining multiple pointer into one so that only one pointer is active at each time */ export function CombinedPointer({ children }: { children?: ReactNode }) { - const pointer = useMemo(() => new CombinedPointerImpl(), []) - usePointerXRSessionVisibility(pointer) - useFrame((state) => pointer.move(state.scene, { timeStamp: performance.now() }), -50) + const pointer = useMemo(() => new CombinedPointerImpl(false), []) + useSetupPointer(pointer) return {children} } +function clearObject(object: Record): void { + for (const key of Object.keys(object)) { + delete object[key] + } +} + /** * hook for creating a grab pointer */ @@ -50,7 +53,8 @@ export function useGrabPointer( pointerType?: string, ): Pointer { const options = useMemo(() => ({}), []) - Object.assign(options, defaultGrabPointerOptions, currentOptions) + clearObject(options) + Object.assign(options, currentOptions) const pointer = useMemo( () => createGrabPointer(spaceRef, pointerState, options, pointerType), [spaceRef, pointerState, options, pointerType], @@ -69,7 +73,8 @@ export function useRayPointer( pointerType?: string, ): Pointer { const options = useMemo(() => ({}), []) - Object.assign(options, defaultRayPointerOptions, currentOptions) + clearObject(options) + Object.assign(options, currentOptions) const pointer = useMemo( () => createRayPointer(spaceRef, pointerState, options, pointerType), [spaceRef, pointerState, options, pointerType], @@ -78,6 +83,26 @@ export function useRayPointer( return pointer } +/** + * hook for creating a ray pointer + */ +export function useLinesPointer( + spaceRef: RefObject, + pointerState: any, + currentOptions?: LinesPointerOptions & { makeDefault?: boolean }, + pointerType?: string, +): Pointer { + const options = useMemo(() => ({}), []) + clearObject(options) + Object.assign(options, currentOptions) + const pointer = useMemo( + () => createLinesPointer(spaceRef, pointerState, options, pointerType), + [spaceRef, pointerState, options, pointerType], + ) + useSetupPointer(pointer, currentOptions?.makeDefault) + return pointer +} + /** * hook for creating a touch pointer */ @@ -88,7 +113,8 @@ export function useTouchPointer( pointerType?: string, ): Pointer { const options = useMemo(() => ({}), []) - Object.assign(options, defaultTouchPointerOptions, currentOptions) + clearObject(options) + Object.assign(options, currentOptions) const pointer = useMemo( () => createTouchPointer(spaceRef, pointerState, options, pointerType), [spaceRef, pointerState, options, pointerType], @@ -151,24 +177,21 @@ export function usePointerXRInputSourceEvents( }, [event, inputSource, pointer, session, missingEvents]) } -function useSetupPointer(pointer: Pointer, makeDefault: boolean = false) { +function useSetupPointer(pointer: Pointer | CombinedPointerImpl, makeDefault: boolean = false) { const combinedPointer = useContext(combinedPointerContext) if (combinedPointer == null) { - // eslint-disable-next-line react-hooks/rules-of-hooks - usePointerXRSessionVisibility(pointer) - // eslint-disable-next-line react-hooks/rules-of-hooks - useFrame((state) => pointer.move(state.scene, { timeStamp: performance.now() }), -50) - } else { - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(() => combinedPointer.register(pointer, makeDefault), [combinedPointer, pointer, makeDefault]) + throw new Error(`xr pointers can only be used inside the XR component`) } - useEffect(() => () => pointer.exit({ timeStamp: performance.now() }), [pointer]) -} - -function usePointerXRSessionVisibility(pointer: Pointer | CombinedPointerImpl) { - const store = useXRStore() - useEffect( - () => setupSyncIsVisible(store, (visible) => pointer.setEnabled(visible, { timeStamp: performance.now() })), - [store, pointer], - ) + useEffect(() => { + const unregister = combinedPointer.register(pointer, makeDefault) + return () => { + unregister() + } + }, [combinedPointer, pointer, makeDefault]) + useEffect(() => { + if (!(pointer instanceof Pointer)) { + return + } + return () => pointer.exit({ timeStamp: performance.now() }) + }, [pointer]) } diff --git a/packages/react/xr/src/xr.tsx b/packages/react/xr/src/xr.tsx index 1e4ff463..459839da 100644 --- a/packages/react/xr/src/xr.tsx +++ b/packages/react/xr/src/xr.tsx @@ -10,10 +10,12 @@ import { DefaultXRScreenInputOptions, } from '@pmndrs/xr/internals' import { Camera, useFrame, useThree, useStore as useRootStore } from '@react-three/fiber' -import { ComponentType, ReactNode, useContext, useEffect } from 'react' +import { ComponentType, ReactNode, useContext, useEffect, useMemo } from 'react' import { useStore } from 'zustand' -import { xrContext } from './contexts.js' +import { combinedPointerContext, xrContext } from './contexts.js' import { XRElements } from './elements.js' +import { setupSyncIsVisible } from '@pmndrs/xr' +import { CombinedPointer } from '@pmndrs/pointer-events' type XRElementImplementation = { /** @@ -97,12 +99,25 @@ export function XR({ children, store }: XRProperties) { useFrame(() => store.onBeforeRender()) return ( - - {children} + + + {children} + ) } +export function RootCombinedPointer({ children }: { children?: ReactNode }) { + const store = useXRStore() + const pointer = useMemo(() => new CombinedPointer(true), []) + useEffect( + () => setupSyncIsVisible(store, (visible) => pointer.setEnabled(visible, { timeStamp: performance.now() })), + [store, pointer], + ) + useFrame((state) => pointer.move(state.scene, { timeStamp: performance.now() }), -50) + return {children} +} + /** * hook for getting the xr store from the context */ diff --git a/packages/xr/src/vanilla/default.ts b/packages/xr/src/vanilla/default.ts index 73d24ebb..8acc3586 100644 --- a/packages/xr/src/vanilla/default.ts +++ b/packages/xr/src/vanilla/default.ts @@ -21,7 +21,6 @@ import { } from '../internals.js' import { XRControllerModel } from './controller.js' import { XRElementImplementations } from './xr.js' -import { setupSyncIsVisible } from '../visible.js' import { DefaultXRControllerOptions, DefaultXRGazeOptions, @@ -38,6 +37,7 @@ import { createGrabPointer, createRayPointer, createTouchPointer, + createLinesPointer, } from '@pmndrs/pointer-events' import { onXRFrame } from './utils.js' import { XRSpaceType } from './types.js' @@ -48,14 +48,14 @@ export function createDefaultXRInputSourceRayPointer( space: Object3D, state: XRInputSourceState, session: XRSession, - options?: DefaultXRInputSourceRayPointerOptions, - combined?: CombinedPointer, + options: DefaultXRInputSourceRayPointerOptions | undefined, + combined: CombinedPointer, makeDefault?: boolean, ) { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace) const pointer = createRayPointer({ current: raySpace }, state, options) - const cleanupPointer = setupPointer(scene, store, pointer, combined, makeDefault) + const unregister = combined.register(pointer, makeDefault) const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events) space.add(raySpace) let undoAddRayModel: (() => void) | undefined @@ -80,7 +80,7 @@ export function createDefaultXRInputSourceRayPointer( undoAddRayModel?.() undoAddCursorModel?.() unbind() - cleanupPointer() + unregister() } } @@ -90,8 +90,8 @@ export function createDefaultXRInputSourceTeleportPointer( space: Object3D, state: XRInputSourceState, session: XRSession, - options?: DefaultXRInputSourceTeleportPointerOptions, - combined?: CombinedPointer, + options: DefaultXRInputSourceTeleportPointerOptions | undefined, + combined: CombinedPointer, makeDefault?: boolean, ) { //the space must be created before the pointer to make sure that the space is updated before the pointer @@ -103,13 +103,13 @@ export function createDefaultXRInputSourceTeleportPointer( onXRFrame((_, delta) => syncTeleportPointerRayGroup(raySpace, teleportPointerRayGroup, delta)) const linePoints = createTeleportRayLine() - const pointer = createRayPointer( + const pointer = createLinesPointer( { current: teleportPointerRayGroup }, state, { ...options, customFilter: buildTeleportTargetFilter(options), linePoints }, 'teleport', ) - const cleanupPointer = setupPointer(scene, store, pointer, combined, makeDefault) + const unregister = combined.register(pointer, makeDefault) const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events) let undoAddRayModel: (() => void) | undefined const { rayModel: rayModelOptions = true, cursorModel: cursorModelOptions = true } = options ?? {} @@ -137,24 +137,10 @@ export function createDefaultXRInputSourceTeleportPointer( undoAddRayModel?.() undoAddCursorModel?.() unbind() - cleanupPointer() + unregister() } } -function setupPointer( - scene: Object3D, - store: XRStore, - pointer: Pointer, - combined: CombinedPointer | undefined, - makeDefault: boolean | undefined, -) { - if (combined != null) { - return combined?.register(pointer, makeDefault ?? false) - } - onXRFrame(() => pointer.move(scene, { timeStamp: performance.now() })) - return setupSyncIsVisible(store, (visible) => pointer.setEnabled(visible, { timeStamp: performance.now() })) -} - export function createDefaultXRInputSourceGrabPointer( scene: Object3D, store: XRStore, @@ -163,14 +149,14 @@ export function createDefaultXRInputSourceGrabPointer( gripSpace: XRSpaceType, session: XRSession, event: 'select' | 'squeeze', - options?: DefaultXRInputSourceGrabPointerOptions, - combined?: CombinedPointer, + options: DefaultXRInputSourceGrabPointerOptions | undefined, + combined: CombinedPointer, makeDefault?: boolean, ) { //the space must be created before the pointer to make sure that the space is updated before the pointer const gripSpaceObject = new XRSpace(gripSpace) const pointer = createGrabPointer({ current: gripSpaceObject }, state, options) - const cleanupPointer = setupPointer(scene, store, pointer, combined, makeDefault) + const unregister = combined.register(pointer, makeDefault) const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, event, state.events) space.add(gripSpaceObject) @@ -183,7 +169,7 @@ export function createDefaultXRInputSourceGrabPointer( undoAddCursorModel = () => scene.remove(cursorModel) } return () => { - cleanupPointer() + unregister() pointer.exit({ timeStamp: performance.now() }) space.remove(gripSpaceObject) undoAddCursorModel?.() @@ -196,14 +182,14 @@ export function createDefaultXRHandTouchPointer( store: XRStore, space: Object3D, state: XRHandState, - options?: DefaultXRHandTouchPointerOptions, - combined?: CombinedPointer, + options: DefaultXRHandTouchPointerOptions | undefined, + combined: CombinedPointer, makeDefault?: boolean, ) { //the space must be created before the pointer to make sure that the space is updated before the pointer const touchSpaceObject = new XRSpace(state.inputSource.hand.get('index-finger-tip')!) const pointer = createTouchPointer({ current: touchSpaceObject }, state, options) - const cleanupPointer = setupPointer(scene, store, pointer, combined, makeDefault) + const unregister = combined.register(pointer, makeDefault) space.add(touchSpaceObject) let undoAddCursorModel: (() => void) | undefined const { cursorModel: cursorModelOptions = true } = options ?? {} @@ -216,7 +202,7 @@ export function createDefaultXRHandTouchPointer( undoAddCursorModel = () => scene.remove(cursorModel) } return () => { - cleanupPointer() + unregister() pointer.exit({ timeStamp: performance.now() }) space.remove(touchSpaceObject) undoAddCursorModel?.() @@ -236,10 +222,10 @@ export function createDefaultXRHand( model: modelOptions = true, touchPointer: touchPointerOptions = true, }: DefaultXRHandOptions = {}, + combined: CombinedPointer, ): () => void { - const combined = new CombinedPointer() - onXRFrame(() => combined.move(scene, { timeStamp: performance.now() })) - setupSyncIsVisible(store, (visible) => combined.setEnabled(visible, { timeStamp: performance.now() })) + const combinedPointer = new CombinedPointer(false) + const unregisterPointer = combined.register(combinedPointer) let destroyRayPointer: (() => void) | undefined if (rayPointerOptions !== false) { @@ -261,7 +247,7 @@ export function createDefaultXRHand( ...spreadable(rayPointerRayModelOptions), }, }, - combined, + combinedPointer, true, ) } @@ -275,7 +261,7 @@ export function createDefaultXRHand( state, session, spreadable(teleportPointerOptions), - combined, + combinedPointer, ) const destroyGrabPointer = grabPointerOptions === false @@ -289,12 +275,12 @@ export function createDefaultXRHand( session, 'select', spreadable(grabPointerOptions), - combined, + combinedPointer, ) const destroyTouchPointer = touchPointerOptions === false ? undefined - : createDefaultXRHandTouchPointer(scene, store, space, state, spreadable(touchPointerOptions), combined) + : createDefaultXRHandTouchPointer(scene, store, space, state, spreadable(touchPointerOptions), combinedPointer) let removeModel: (() => void) | undefined if (modelOptions !== false) { const model = new XRHandModel(state.inputSource.hand, state.assetPath, spreadable(modelOptions)) @@ -302,6 +288,7 @@ export function createDefaultXRHand( removeModel = () => space.remove(model) } return () => { + unregisterPointer() destroyRayPointer?.() destroyGrabPointer?.() destroyTouchPointer?.() @@ -322,10 +309,10 @@ export function createDefaultXRController( teleportPointer: teleportPointerOptions = false, model: modelOptions = true, }: DefaultXRControllerOptions = {}, + combined: CombinedPointer, ): () => void { - const combined = new CombinedPointer() - onXRFrame(() => combined.move(scene, { timeStamp: performance.now() })) - setupSyncIsVisible(store, (visible) => combined.setEnabled(visible, { timeStamp: performance.now() })) + const combinedPointer = new CombinedPointer(true) + const unregisterPointer = combined.register(combinedPointer) const destroyRayPointer = rayPointerOptions === false ? undefined @@ -336,7 +323,7 @@ export function createDefaultXRController( state, session, { minDistance: 0.2, ...spreadable(rayPointerOptions) }, - combined, + combinedPointer, true, ) @@ -350,7 +337,7 @@ export function createDefaultXRController( state, session, spreadable(teleportPointerOptions), - combined, + combinedPointer, ) const destroyGrabPointer = grabPointerOptions === false @@ -364,7 +351,7 @@ export function createDefaultXRController( session, 'squeeze', spreadable(grabPointerOptions), - combined, + combinedPointer, ) let removeModel: (() => void) | undefined @@ -374,6 +361,7 @@ export function createDefaultXRController( removeModel = () => space.remove(model) } return () => { + unregisterPointer() destroyTeleportPointer?.() destroyRayPointer?.() destroyGrabPointer?.() @@ -387,14 +375,13 @@ export function createDefaultXRTransientPointer( space: Object3D, state: XRInputSourceState, session: XRSession, - options?: DefaultXRTransientPointerOptions, - combined?: CombinedPointer, - makeDefault?: boolean, + options: DefaultXRTransientPointerOptions | undefined, + combined: CombinedPointer, ): () => void { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace) const pointer = createRayPointer({ current: raySpace }, state, options) - const cleanupPointer = setupPointer(scene, store, pointer, combined, makeDefault) + const unregister = combined.register(pointer) const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events) space.add(raySpace) let undoAddCursorModel: (() => void) | undefined @@ -408,7 +395,7 @@ export function createDefaultXRTransientPointer( undoAddCursorModel = () => scene.remove(cursorModel) } return () => { - cleanupPointer() + unregister() pointer.exit({ timeStamp: performance.now() }) space.remove(raySpace) undoAddCursorModel?.() @@ -422,12 +409,13 @@ export function createDefaultXRGaze( space: Object3D, state: XRInputSourceState, session: XRSession, - options?: DefaultXRGazeOptions, + options: DefaultXRGazeOptions | undefined, + combined: CombinedPointer, ): () => void { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace) const pointer = createRayPointer({ current: raySpace }, state, options) - const cleanupPointer = setupPointer(scene, store, pointer, undefined, undefined) + const unregister = combined.register(pointer) const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events) space.add(raySpace) let undoAddCursorModel: (() => void) | undefined @@ -441,7 +429,7 @@ export function createDefaultXRGaze( undoAddCursorModel = () => scene.remove(cursorModel) } return () => { - cleanupPointer() + unregister() pointer.exit({ timeStamp: performance.now() }) space.remove(raySpace) undoAddCursorModel?.() @@ -455,16 +443,17 @@ export function createDefaultXRScreenInput( space: Object3D, state: XRInputSourceState, session: XRSession, - options?: DefaultXRScreenInputOptions, + options: DefaultXRScreenInputOptions | undefined, + combined: CombinedPointer, ): () => void { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace) const pointer = createRayPointer({ current: raySpace }, state, options) - const cleanupPointer = setupPointer(scene, store, pointer, undefined, undefined) + const unregister = combined.register(pointer) const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events) space.add(raySpace) return () => { - cleanupPointer() + unregister() space.remove(raySpace) pointer.exit({ timeStamp: performance.now() }) unbind() diff --git a/packages/xr/src/vanilla/elements.ts b/packages/xr/src/vanilla/elements.ts index 8db521b6..ed5903ed 100644 --- a/packages/xr/src/vanilla/elements.ts +++ b/packages/xr/src/vanilla/elements.ts @@ -11,6 +11,8 @@ import { createDefaultXRTransientPointer, } from './default.js' import { XRSpaceType } from './types.js' +import { CombinedPointer } from '@pmndrs/pointer-events' +import { setupSyncIsVisible } from '../visible.js' export function setupSyncXRElements( scene: Object3D, @@ -18,6 +20,10 @@ export function setupSyncXRElements( target: Object3D, updatesList: XRUpdatesList, ): () => void { + const combined = new CombinedPointer(true) + const onFrame = () => combined.move(scene, { timeStamp: performance.now() }) + updatesList.push(onFrame) + setupSyncIsVisible(store, (visible) => combined.setEnabled(visible, { timeStamp: performance.now() })) const inputGroup = new Group() const syncControllers = setupSyncInputSourceElements( createDefaultXRController, @@ -26,9 +32,26 @@ export function setupSyncXRElements( 'controller', inputGroup, updatesList, + combined, + ) + const syncGazes = setupSyncInputSourceElements( + createDefaultXRGaze, + scene, + store, + 'gaze', + inputGroup, + updatesList, + combined, + ) + const syncHands = setupSyncInputSourceElements( + createDefaultXRHand, + scene, + store, + 'hand', + inputGroup, + updatesList, + combined, ) - const syncGazes = setupSyncInputSourceElements(createDefaultXRGaze, scene, store, 'gaze', inputGroup, updatesList) - const syncHands = setupSyncInputSourceElements(createDefaultXRHand, scene, store, 'hand', inputGroup, updatesList) const syncScreenInputs = setupSyncInputSourceElements( createDefaultXRScreenInput, scene, @@ -36,6 +59,7 @@ export function setupSyncXRElements( 'screenInput', inputGroup, updatesList, + combined, ) const syncTransientPointers = setupSyncInputSourceElements( createDefaultXRTransientPointer, @@ -44,6 +68,7 @@ export function setupSyncXRElements( 'transientPointer', inputGroup, updatesList, + combined, ) const unsubscribe = store.subscribe((s, prev) => { inputGroup.visible = s.visibilityState === 'visible' @@ -61,6 +86,11 @@ export function setupSyncXRElements( }) target.add(inputGroup) return () => { + const index = updatesList.indexOf(onFrame) + if (index === -1) { + return + } + updatesList.splice(index, 1) target.remove(inputGroup) unsubscribe() syncControllers(undefined, [], [], false, false) @@ -78,13 +108,15 @@ function setupSyncInputSourceElements( space: Object3D, state: any, session: XRSession, - options?: any, + options: any, + combined: CombinedPointer, ) => void, scene: Object3D, store: XRStore, key: K, target: Object3D, updatesList: XRUpdatesList, + combined: CombinedPointer, ) { return setupSync[K]>(key, (session, state, implementationInfo) => runInXRUpdatesListContext(updatesList, () => { @@ -100,7 +132,7 @@ function setupSyncInputSourceElements( target.add(spaceObject) const customCleanup = typeof implementation === 'object' - ? defaultCreate(scene, store, spaceObject, state, session, implementation) + ? defaultCreate(scene, store, spaceObject, state, session, implementation, combined) : implementation?.(store, spaceObject, state as any, session) return () => { target.remove(spaceObject) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e68a88d..9bcc56ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: version: 0.1.1 '@react-three/fiber': specifier: ^8.16.6 - version: 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@types/eslint': specifier: ^8.56.10 - version: 8.56.11 + version: 8.56.12 '@types/react': specifier: ^18.3.3 - version: 18.3.3 + version: 18.3.5 '@types/react-dom': specifier: ^18.3.0 version: 18.3.0 @@ -28,7 +28,7 @@ importers: version: 0.164.1 '@types/webxr': specifier: ^0.5.16 - version: 0.5.19 + version: 0.5.20 '@typescript-eslint/eslint-plugin': specifier: ^7.12.0 version: 7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4) @@ -37,10 +37,10 @@ importers: version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) '@vitejs/plugin-basic-ssl': specifier: ^1.1.0 - version: 1.1.0(vite@5.4.1) + version: 1.1.0(vite@5.4.2) '@vitejs/plugin-react': specifier: ^4.3.0 - version: 4.3.1(vite@5.4.1) + version: 4.3.1(vite@5.4.2) eslint: specifier: ^8 version: 8.57.0 @@ -52,7 +52,7 @@ importers: version: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint@8.57.0) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.2.1(@types/eslint@8.56.11)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.3.3) + version: 5.2.1(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.3.3) eslint-plugin-react: specifier: ^7.34.2 version: 7.35.0(eslint@8.57.0) @@ -79,13 +79,13 @@ importers: version: 5.5.4 vite: specifier: ^5.2.11 - version: 5.4.1(@types/node@20.15.0) + version: 5.4.2(@types/node@20.16.3) examples/handheld-ar: dependencies: '@react-three/drei': specifier: ^9.109.2 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -97,7 +97,7 @@ importers: version: link:../../packages/react/xr zustand: specifier: ^4.5.2 - version: 4.5.5(@types/react@18.3.3)(react@18.3.1) + version: 4.5.5(@types/react@18.3.5)(react@18.3.1) examples/layers: dependencies: @@ -106,7 +106,7 @@ importers: version: link:../../packages/pointer-events '@react-three/drei': specifier: ^9.111.0 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -115,43 +115,43 @@ importers: dependencies: '@react-three/drei': specifier: ^9.108.4 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/rapier': specifier: ^1.4.0 - version: 1.4.0(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1) + version: 1.4.0(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr zustand: specifier: ^4.5.4 - version: 4.5.5(@types/react@18.3.3)(react@18.3.1) + version: 4.5.5(@types/react@18.3.5)(react@18.3.1) examples/miniature: dependencies: '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr zustand: specifier: ^4.5.2 - version: 4.5.5(@types/react@18.3.3)(react@18.3.1) + version: 4.5.5(@types/react@18.3.5)(react@18.3.1) examples/pingpong: dependencies: '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/rapier': specifier: ^1.4.0 - version: 1.4.0(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1) + version: 1.4.0(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr valtio: specifier: ^1.13.2 - version: 1.13.2(@types/react@18.3.3)(react@18.3.1) + version: 1.13.2(@types/react@18.3.5)(react@18.3.1) examples/pointer-events: dependencies: @@ -163,10 +163,10 @@ importers: dependencies: '@react-three/cannon': specifier: ^6.6.0 - version: 6.6.0(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1)(typescript@5.5.4) + version: 6.6.0(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1)(typescript@5.5.4) '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -181,7 +181,7 @@ importers: dependencies: '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -190,7 +190,7 @@ importers: dependencies: '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -202,13 +202,13 @@ importers: dependencies: '@react-three/drei': specifier: ^9.109.5 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/uikit': specifier: ^0.4.1 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/uikit-lucide': specifier: ^0.4.1 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -217,7 +217,7 @@ importers: dependencies: '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -225,20 +225,20 @@ importers: examples/uikit: dependencies: '@pmndrs/pointer-events': - specifier: ^6.1.0 - version: 6.1.1 + specifier: workspace:^ + version: link:../../packages/pointer-events '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/uikit': specifier: ^0.4.0 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/uikit-default': specifier: ^0.4.0 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/uikit-lucide': specifier: ^0.4.0 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -262,13 +262,13 @@ importers: version: 1.8.0 '@react-three/drei': specifier: ^9.108.3 - version: 9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@react-three/uikit': specifier: ^0.4.1 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/uikit-lucide': specifier: ^0.4.1 - version: 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + version: 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) '@react-three/xr': specifier: workspace:^ version: link:../../packages/react/xr @@ -277,7 +277,7 @@ importers: devDependencies: '@types/node': specifier: ^20.12.11 - version: 20.15.0 + version: 20.16.3 '@types/three': specifier: ^0.164.0 version: 0.164.1 @@ -286,10 +286,10 @@ importers: version: 0.164.1 vite: specifier: ^5.2.11 - version: 5.4.1(@types/node@20.15.0) + version: 5.4.2(@types/node@20.16.3) vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.15.0) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.16.3) packages/react/xr: dependencies: @@ -301,7 +301,7 @@ importers: version: link:../../xr '@react-three/fiber': specifier: '>=8' - version: 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + version: 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) react: specifier: '>=18' version: 18.3.1 @@ -316,29 +316,29 @@ importers: version: 0.167.1 tunnel-rat: specifier: ^0.1.2 - version: 0.1.2(@types/react@18.3.3)(react@18.3.1) + version: 0.1.2(@types/react@18.3.5)(react@18.3.1) zustand: specifier: ^4.5.2 - version: 4.5.5(@types/react@18.3.3)(react@18.3.1) + version: 4.5.5(@types/react@18.3.5)(react@18.3.1) devDependencies: '@vitejs/plugin-react': specifier: ^4.3.0 - version: 4.3.1(vite@5.4.1) + version: 4.3.1(vite@5.4.2) vite: specifier: ^5.2.11 - version: 5.4.1(@types/node@20.15.0) + version: 5.4.2(@types/node@20.16.3) packages/xr: dependencies: '@iwer/devui': specifier: ^0.1.0 - version: 0.1.1(iwer@1.0.3) + version: 0.1.1(iwer@1.0.4) '@pmndrs/pointer-events': specifier: workspace:^ version: link:../pointer-events iwer: specifier: ^1.0.3 - version: 1.0.3 + version: 1.0.4 meshline: specifier: ^3.3.1 version: 3.3.1(three@0.167.1) @@ -347,7 +347,7 @@ importers: version: 0.167.1 zustand: specifier: ^4.5.2 - version: 4.5.5(@types/react@18.3.3)(react@18.3.1) + version: 4.5.5(@types/react@18.3.5)(react@18.3.1) packages: @@ -372,8 +372,8 @@ packages: picocolors: 1.0.1 dev: true - /@babel/compat-data@7.25.2: - resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} + /@babel/compat-data@7.25.4: + resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} engines: {node: '>=6.9.0'} dev: true @@ -383,14 +383,14 @@ packages: dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.0 + '@babel/generator': 7.25.6 '@babel/helper-compilation-targets': 7.25.2 '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helpers': 7.25.0 - '@babel/parser': 7.25.3 + '@babel/helpers': 7.25.6 + '@babel/parser': 7.25.6 '@babel/template': 7.25.0 - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.2 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 convert-source-map: 2.0.0 debug: 4.3.6 gensync: 1.0.0-beta.2 @@ -400,11 +400,11 @@ packages: - supports-color dev: true - /@babel/generator@7.25.0: - resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} + /@babel/generator@7.25.6: + resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 @@ -414,7 +414,7 @@ packages: resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.25.2 + '@babel/compat-data': 7.25.4 '@babel/helper-validator-option': 7.24.8 browserslist: 4.23.3 lru-cache: 5.1.1 @@ -425,8 +425,8 @@ packages: resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.2 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 transitivePeerDependencies: - supports-color dev: true @@ -441,7 +441,7 @@ packages: '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.3 + '@babel/traverse': 7.25.6 transitivePeerDependencies: - supports-color dev: true @@ -455,8 +455,8 @@ packages: resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.2 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 transitivePeerDependencies: - supports-color dev: true @@ -476,12 +476,12 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helpers@7.25.0: - resolution: {integrity: sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==} + /@babel/helpers@7.25.6: + resolution: {integrity: sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.0 - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 dev: true /@babel/highlight@7.24.7: @@ -494,12 +494,12 @@ packages: picocolors: 1.0.1 dev: true - /@babel/parser@7.25.3: - resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} + /@babel/parser@7.25.6: + resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 dev: true /@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2): @@ -522,8 +522,8 @@ packages: '@babel/helper-plugin-utils': 7.24.8 dev: true - /@babel/runtime@7.25.0: - resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==} + /@babel/runtime@7.25.6: + resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 @@ -533,27 +533,27 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 dev: true - /@babel/traverse@7.25.3: - resolution: {integrity: sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==} + /@babel/traverse@7.25.6: + resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.0 - '@babel/parser': 7.25.3 + '@babel/generator': 7.25.6 + '@babel/parser': 7.25.6 '@babel/template': 7.25.0 - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types@7.25.2: - resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} + /@babel/types@7.25.6: + resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.24.8 @@ -910,7 +910,7 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: false - /@iwer/devui@0.1.1(iwer@1.0.3): + /@iwer/devui@0.1.1(iwer@1.0.4): resolution: {integrity: sha512-kZnoWc7KeKsdhRM7n7KORRlNCtk5Y2NmCsDUnwRvFyY1Cv0y0bnf3QMN6+rnHtRH88PtNxqKpN+syN8byrCAgw==} peerDependencies: iwer: ^1.0.3 @@ -918,20 +918,13 @@ packages: '@fortawesome/fontawesome-svg-core': 6.6.0 '@fortawesome/free-solid-svg-icons': 6.6.0 '@fortawesome/react-fontawesome': 0.2.2(@fortawesome/fontawesome-svg-core@6.6.0)(react@18.3.1) - iwer: 1.0.3 + iwer: 1.0.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-components: 6.1.12(react-dom@18.3.1)(react@18.3.1) + styled-components: 6.1.13(react-dom@18.3.1)(react@18.3.1) three: 0.166.1 dev: false - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.27.8 - dev: true - /@jridgewell/gen-mapping@0.3.5: resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1008,10 +1001,6 @@ packages: three: 0.167.1 dev: false - /@pmndrs/pointer-events@6.1.1: - resolution: {integrity: sha512-baOSEYLKF98mxRuf7Mhdy5Cv5H5VexKnmbil6sb8EISfl8lWKhckZ88B7esCXG18Te+sOAO2ZqLGtxr/SqrJ+Q==} - dev: false - /@pmndrs/uikit-lucide@0.4.4(three@0.167.1): resolution: {integrity: sha512-us9lzwtG7gByDmYfqZ9KXeSnbMRWTuHXBdLWsCtmuu1mYk91UZlqsBdvLo0GsC8SDHz3S7Y49Hlgtsw4OcRZ/Q==} dependencies: @@ -1027,7 +1016,7 @@ packages: three: '>=0.160' dependencies: '@preact/signals-core': 1.8.0 - inline-style-parser: 0.2.3 + inline-style-parser: 0.2.4 node-html-parser: 6.1.13 three: 0.167.1 tw-to-css: 0.0.12 @@ -1076,7 +1065,7 @@ packages: react: 18.3.1 dev: false - /@react-spring/three@9.6.1(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1): + /@react-spring/three@9.6.1(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1): resolution: {integrity: sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==} peerDependencies: '@react-three/fiber': '>=6.0' @@ -1087,7 +1076,7 @@ packages: '@react-spring/core': 9.6.1(react@18.3.1) '@react-spring/shared': 9.6.1(react@18.3.1) '@react-spring/types': 9.6.1 - '@react-three/fiber': 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + '@react-three/fiber': 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) react: 18.3.1 three: 0.167.1 dev: false @@ -1096,7 +1085,7 @@ packages: resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==} dev: false - /@react-three/cannon@6.6.0(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1)(typescript@5.5.4): + /@react-three/cannon@6.6.0(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1)(typescript@5.5.4): resolution: {integrity: sha512-lP9rJoVHQi0w+dYF8FJAm2xr5eLfNEckb04j72kjqndUkuOPr26N4rSBhQbHl5b5N3tEnhQaIMungAvHkcY8/A==} peerDependencies: '@react-three/fiber': '>=8' @@ -1104,7 +1093,7 @@ packages: three: '>=0.139' dependencies: '@pmndrs/cannon-worker-api': 2.4.0(three@0.167.1) - '@react-three/fiber': 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + '@react-three/fiber': 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) cannon-es: 0.20.0 cannon-es-debugger: 1.0.0(cannon-es@0.20.0)(three@0.167.1)(typescript@5.5.4) react: 18.3.1 @@ -1113,8 +1102,8 @@ packages: - typescript dev: false - /@react-three/drei@9.111.0(@react-three/fiber@8.17.5)(@types/react@18.3.3)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1): - resolution: {integrity: sha512-NQnbyjxERulrWPX/rdT0gFnBqKkk745O1e14Zj1WPbwbyjzwIlzbtLGreSSC4HgbAMrUV4xjIND9EKMfFHLprg==} + /@react-three/drei@9.111.5(@react-three/fiber@8.17.6)(@types/react@18.3.5)(@types/three@0.164.1)(react-dom@18.3.1)(react@18.3.1)(three@0.167.1): + resolution: {integrity: sha512-BybZGYPrODhFYx0OeGGllMU5zc/yRDzXHdHWyoGX61HVyMqPwxma4xyj+ejKdXncQLk0ntvgodDg9lgS4P/hGQ==} peerDependencies: '@react-three/fiber': '>=8.0' react: '>=18.0' @@ -1124,15 +1113,15 @@ packages: react-dom: optional: true dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.25.6 '@mediapipe/tasks-vision': 0.10.8 '@monogrid/gainmap-js': 3.0.5(three@0.167.1) - '@react-spring/three': 9.6.1(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1) - '@react-three/fiber': 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + '@react-spring/three': 9.6.1(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1) + '@react-three/fiber': 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) '@use-gesture/react': 10.3.1(react@18.3.1) - camera-controls: 2.8.5(three@0.167.1) + camera-controls: 2.9.0(three@0.167.1) cross-env: 7.0.3 - detect-gpu: 5.0.43 + detect-gpu: 5.0.46 glsl-noise: 0.0.0 hls.js: 1.3.5 maath: 0.10.8(@types/three@0.164.1)(three@0.167.1) @@ -1147,7 +1136,7 @@ packages: three-mesh-bvh: 0.7.6(three@0.167.1) three-stdlib: 2.32.2(three@0.167.1) troika-three-text: 0.49.1(three@0.167.1) - tunnel-rat: 0.1.2(@types/react@18.3.3)(react@18.3.1) + tunnel-rat: 0.1.2(@types/react@18.3.5)(react@18.3.1) utility-types: 3.11.0 uuid: 9.0.1 zustand: 3.7.2(react@18.3.1) @@ -1160,14 +1149,14 @@ packages: /@react-three/eslint-plugin@0.1.1: resolution: {integrity: sha512-n2tGHNhF3yzTar7lJkJPZeWFPH3C0jKb0Iws4zMelOQErqG3+xIfw3n8GQzL6olxuga/FP1tENl+UA+d6aa6Hg==} dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.25.6 eslint: 8.57.0 transitivePeerDependencies: - supports-color dev: true - /@react-three/fiber@8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1): - resolution: {integrity: sha512-7uqtTWQrNIKW6wbgF0CQiDuo7uHoRd96lGBKsdRa+j/s268kqO4MBsxynLUpg6F/+mir5SEt9zJ3Up+lOjz/dg==} + /@react-three/fiber@8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1): + resolution: {integrity: sha512-RqZXSpEVY8alF3dWgFhUFePM9FE9jCZxeZJ3wEJ8z6Bd6AsrLXXs9wRW6WhCY/r0y7eW36v2t74QavM0coA3aA==} peerDependencies: expo: '>=43.0' expo-asset: '>=8.4' @@ -1191,10 +1180,10 @@ packages: react-native: optional: true dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.25.6 '@types/debounce': 1.2.4 '@types/react-reconciler': 0.26.7 - '@types/webxr': 0.5.19 + '@types/webxr': 0.5.20 base64-js: 1.5.1 buffer: 6.0.3 debounce: 1.2.1 @@ -1207,7 +1196,7 @@ packages: three: 0.167.1 zustand: 3.7.2(react@18.3.1) - /@react-three/rapier@1.4.0(@react-three/fiber@8.17.5)(react@18.3.1)(three@0.167.1): + /@react-three/rapier@1.4.0(@react-three/fiber@8.17.6)(react@18.3.1)(three@0.167.1): resolution: {integrity: sha512-mQryvEx9mW6u0bHo3KDc7G6gURjKSOA1I+W9EVNDJmI8hC+HoS9WyDzc8QnXyMYmvUAMEqOQImECNXAtoLacHw==} peerDependencies: '@react-three/fiber': '>=8.9.0' @@ -1215,19 +1204,19 @@ packages: three: '>=0.139.0' dependencies: '@dimforge/rapier3d-compat': 0.13.1 - '@react-three/fiber': 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + '@react-three/fiber': 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) react: 18.3.1 suspend-react: 0.1.3(react@18.3.1) three: 0.167.1 three-stdlib: 2.23.9(three@0.167.1) dev: false - /@react-three/uikit-default@0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1): + /@react-three/uikit-default@0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1): resolution: {integrity: sha512-Y+6nfymUqHZUX4b777DwTn4I/4M5m1hBBn69oVJ4IwpPICkCtn4y0K8iolg0NjGfkbsDzCYIF8Y7xcXwo0VoKQ==} dependencies: - '@react-three/uikit': 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) - '@react-three/uikit-lucide': 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) - tunnel-rat: 0.1.2(@types/react@18.3.3)(react@18.3.1) + '@react-three/uikit': 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) + '@react-three/uikit-lucide': 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) + tunnel-rat: 0.1.2(@types/react@18.3.5)(react@18.3.1) transitivePeerDependencies: - '@react-three/fiber' - '@types/react' @@ -1237,10 +1226,10 @@ packages: - ts-node dev: false - /@react-three/uikit-lucide@0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1): + /@react-three/uikit-lucide@0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1): resolution: {integrity: sha512-XwOnuZOXiItfR9NV2eYblzaE29p/JMGqpcad0Sds4nY9YYd373jcrpKYK2f0yYCb33nZvjK22mo4cMQx7F2oUQ==} dependencies: - '@react-three/uikit': 0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1) + '@react-three/uikit': 0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1) transitivePeerDependencies: - '@react-three/fiber' - '@types/react' @@ -1250,7 +1239,7 @@ packages: - ts-node dev: false - /@react-three/uikit@0.4.4(@react-three/fiber@8.17.5)(@types/react@18.3.3)(react@18.3.1)(three@0.167.1): + /@react-three/uikit@0.4.4(@react-three/fiber@8.17.6)(@types/react@18.3.5)(react@18.3.1)(three@0.167.1): resolution: {integrity: sha512-V3f+8ACsx1Ia60yym1ltuufT8eyJZaMEFqBNQ/3awSGk22wL6sWuqvFnq1g2jm8xvPjE4YseFVPxnH5Qx+08CQ==} hasBin: true peerDependencies: @@ -1259,15 +1248,15 @@ packages: dependencies: '@pmndrs/uikit': 0.4.4(three@0.167.1) '@preact/signals-core': 1.8.0 - '@react-three/fiber': 8.17.5(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) + '@react-three/fiber': 8.17.6(react-dom@18.3.1)(react@18.3.1)(three@0.167.1) chalk: 5.3.0 commander: 12.1.0 - ora: 8.0.1 + ora: 8.1.0 prettier: 3.3.3 prompts: 2.4.2 react: 18.3.1 zod: 3.23.8 - zustand: 4.5.5(@types/react@18.3.3)(react@18.3.1) + zustand: 4.5.5(@types/react@18.3.5)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer @@ -1275,146 +1264,142 @@ packages: - ts-node dev: false - /@rollup/rollup-android-arm-eabi@4.20.0: - resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} + /@rollup/rollup-android-arm-eabi@4.21.2: + resolution: {integrity: sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.20.0: - resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} + /@rollup/rollup-android-arm64@4.21.2: + resolution: {integrity: sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.20.0: - resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} + /@rollup/rollup-darwin-arm64@4.21.2: + resolution: {integrity: sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.20.0: - resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} + /@rollup/rollup-darwin-x64@4.21.2: + resolution: {integrity: sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.20.0: - resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} + /@rollup/rollup-linux-arm-gnueabihf@4.21.2: + resolution: {integrity: sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf@4.20.0: - resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} + /@rollup/rollup-linux-arm-musleabihf@4.21.2: + resolution: {integrity: sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.20.0: - resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} + /@rollup/rollup-linux-arm64-gnu@4.21.2: + resolution: {integrity: sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.20.0: - resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} + /@rollup/rollup-linux-arm64-musl@4.21.2: + resolution: {integrity: sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.20.0: - resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} + /@rollup/rollup-linux-powerpc64le-gnu@4.21.2: + resolution: {integrity: sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==} cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.20.0: - resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} + /@rollup/rollup-linux-riscv64-gnu@4.21.2: + resolution: {integrity: sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.20.0: - resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} + /@rollup/rollup-linux-s390x-gnu@4.21.2: + resolution: {integrity: sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.20.0: - resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} + /@rollup/rollup-linux-x64-gnu@4.21.2: + resolution: {integrity: sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.20.0: - resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} + /@rollup/rollup-linux-x64-musl@4.21.2: + resolution: {integrity: sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.20.0: - resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} + /@rollup/rollup-win32-arm64-msvc@4.21.2: + resolution: {integrity: sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.20.0: - resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} + /@rollup/rollup-win32-ia32-msvc@4.21.2: + resolution: {integrity: sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.20.0: - resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} + /@rollup/rollup-win32-x64-msvc@4.21.2: + resolution: {integrity: sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true - /@tweenjs/tween.js@23.1.3: resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 @@ -1423,20 +1408,20 @@ packages: /@types/babel__generator@7.6.8: resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 dev: true /@types/babel__template@7.4.4: resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 dev: true /@types/babel__traverse@7.20.6: resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 dev: true /@types/debounce@1.2.4: @@ -1446,8 +1431,8 @@ packages: resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} dev: false - /@types/eslint@8.56.11: - resolution: {integrity: sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==} + /@types/eslint@8.56.12: + resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==} dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 @@ -1465,10 +1450,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/node@20.15.0: - resolution: {integrity: sha512-eQf4OkH6gA9v1W0iEpht/neozCsZKMTK+C4cU6/fv7wtJCCL8LEQ4hie2Ln8ZP/0YYM2xGj7//f8xyqItkJ6QA==} + /@types/node@20.16.3: + resolution: {integrity: sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==} dependencies: - undici-types: 6.13.0 + undici-types: 6.19.8 dev: true /@types/offscreencanvas@2019.7.3: @@ -1481,21 +1466,21 @@ packages: /@types/react-dom@18.3.0: resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.5 dev: true /@types/react-reconciler@0.26.7: resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==} dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.5 /@types/react-reconciler@0.28.8: resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==} dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.5 - /@types/react@18.3.3: - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + /@types/react@18.3.5: + resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==} dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 @@ -1512,7 +1497,7 @@ packages: dependencies: '@tweenjs/tween.js': 23.1.3 '@types/stats.js': 0.17.3 - '@types/webxr': 0.5.19 + '@types/webxr': 0.5.20 fflate: 0.8.2 meshoptimizer: 0.18.1 dev: false @@ -1522,12 +1507,12 @@ packages: dependencies: '@tweenjs/tween.js': 23.1.3 '@types/stats.js': 0.17.3 - '@types/webxr': 0.5.19 + '@types/webxr': 0.5.20 fflate: 0.8.2 meshoptimizer: 0.18.1 - /@types/webxr@0.5.19: - resolution: {integrity: sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==} + /@types/webxr@0.5.20: + resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} /@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4): resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} @@ -1673,16 +1658,16 @@ packages: react: 18.3.1 dev: false - /@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.1): + /@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.2): resolution: {integrity: sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==} engines: {node: '>=14.6.0'} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: - vite: 5.4.1(@types/node@20.15.0) + vite: 5.4.2(@types/node@20.16.3) dev: true - /@vitejs/plugin-react@4.3.1(vite@5.4.1): + /@vitejs/plugin-react@4.3.1(vite@5.4.2): resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -1693,48 +1678,54 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.1(@types/node@20.15.0) + vite: 5.4.2(@types/node@20.16.3) transitivePeerDependencies: - supports-color dev: true - /@vitest/expect@1.6.0: - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + /@vitest/expect@2.0.5: + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + dev: true + + /@vitest/pretty-format@2.0.5: + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.5.0 + tinyrainbow: 1.2.0 dev: true - /@vitest/runner@1.6.0: - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + /@vitest/runner@2.0.5: + resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 + '@vitest/utils': 2.0.5 pathe: 1.1.2 dev: true - /@vitest/snapshot@1.6.0: - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + /@vitest/snapshot@2.0.5: + resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} dependencies: + '@vitest/pretty-format': 2.0.5 magic-string: 0.30.11 pathe: 1.1.2 - pretty-format: 29.7.0 dev: true - /@vitest/spy@1.6.0: - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + /@vitest/spy@2.0.5: + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} dependencies: - tinyspy: 2.2.1 + tinyspy: 3.0.0 dev: true - /@vitest/utils@1.6.0: - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + /@vitest/utils@2.0.5: + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} dependencies: - diff-sequences: 29.6.3 + '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + loupe: 3.1.1 + tinyrainbow: 1.2.0 dev: true /acorn-jsx@5.3.2(acorn@8.12.1): @@ -1745,13 +1736,6 @@ packages: acorn: 8.12.1 dev: true - /acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} - engines: {node: '>=0.4.0'} - dependencies: - acorn: 8.12.1 - dev: true - /acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} @@ -1789,11 +1773,6 @@ packages: dependencies: color-convert: 2.0.1 - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true - /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -1913,8 +1892,9 @@ packages: is-shared-array-buffer: 1.0.3 dev: true - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} dev: true /available-typed-arrays@1.0.7: @@ -1968,8 +1948,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001651 - electron-to-chromium: 1.5.10 + caniuse-lite: 1.0.30001655 + electron-to-chromium: 1.5.13 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) dev: true @@ -2010,16 +1990,16 @@ packages: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: false - /camera-controls@2.8.5(three@0.167.1): - resolution: {integrity: sha512-7VTwRk7Nu1nRKsY7bEt9HVBfKt8DETvzyYhLN4OW26OByBayMDB5fUaNcPI+z++vG23RH5yqn6ZRhZcgLQy2rA==} + /camera-controls@2.9.0(three@0.167.1): + resolution: {integrity: sha512-TpCujnP0vqPppTXXJRYpvIy0xq9Tro6jQf2iYUxlDpPCNxkvE/XGaTuwIxnhINOkVP/ob2CRYXtY3iVYXeMEzA==} peerDependencies: three: '>=0.126.1' dependencies: three: 0.167.1 dev: false - /caniuse-lite@1.0.30001651: - resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} + /caniuse-lite@1.0.30001655: + resolution: {integrity: sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==} dev: true /cannon-es-debugger@1.0.0(cannon-es@0.20.0)(three@0.167.1)(typescript@5.5.4): @@ -2041,17 +2021,15 @@ packages: resolution: {integrity: sha512-eZhWTZIkFOnMAJOgfXJa9+b3kVlvG+FX4mdkpePev/w/rP5V8NRquGyEozcjPfEoXUlb+p7d9SUcmDSn14prOA==} dev: false - /chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} + /chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.1.0 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 dev: true /chalk@2.4.2: @@ -2076,10 +2054,9 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: false - /check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - dependencies: - get-func-name: 2.0.2 + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} dev: true /chevrotain@10.5.0: @@ -2108,11 +2085,11 @@ packages: fsevents: 2.3.3 dev: false - /cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} dependencies: - restore-cursor: 4.0.0 + restore-cursor: 5.1.0 dev: false /cli-spinners@2.9.2: @@ -2153,10 +2130,6 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - dev: true - /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true @@ -2267,11 +2240,9 @@ packages: ms: 2.1.2 dev: true - /deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - dependencies: - type-detect: 4.1.0 dev: true /deep-is@0.1.4: @@ -2301,11 +2272,11 @@ packages: peerDependencies: valtio: '*' dependencies: - valtio: 1.13.2(@types/react@18.3.3)(react@18.3.1) + valtio: 1.13.2(@types/react@18.3.5)(react@18.3.1) dev: false - /detect-gpu@5.0.43: - resolution: {integrity: sha512-KVcUS/YzsZIBIACz6p2xpuBpAjaY4wiELImJ7M8rb9i16NE6frnVpSV/UBpkK6DYj4Wd3NJeE4sghcaypuM8bg==} + /detect-gpu@5.0.46: + resolution: {integrity: sha512-aulQlEJDVAADo2j4ZkcEu/mtuX9dz104w7uIDa52/ntcKdOEM8aI+k91Wv4x0o+Gds4Nbd2Sds0Uaqp1ZuLLJw==} dependencies: webgl-constants: 1.1.1 dev: false @@ -2314,11 +2285,6 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: false - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2379,12 +2345,12 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: false - /electron-to-chromium@1.5.10: - resolution: {integrity: sha512-C3RDERDjrNW262GCRvpoer3a0Ksd66CtgDLxMHhzShQ8fhL4kwnpVXsJPAKg9xJjIROXUbLBrvtOzVAjALMIWA==} + /electron-to-chromium@1.5.13: + resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} dev: true - /emoji-regex@10.3.0: - resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + /emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} dev: false /emoji-regex@8.0.0: @@ -2546,8 +2512,8 @@ packages: '@esbuild/win32-x64': 0.21.5 dev: true - /escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} dev: true @@ -2573,14 +2539,14 @@ packages: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: debug: 3.2.7 - is-core-module: 2.15.0 + is-core-module: 2.15.1 resolve: 1.22.8 transitivePeerDependencies: - supports-color dev: true - /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + /eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + resolution: {integrity: sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2627,9 +2593,9 @@ packages: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 - is-core-module: 2.15.0 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 @@ -2643,7 +2609,7 @@ packages: - supports-color dev: true - /eslint-plugin-prettier@5.2.1(@types/eslint@8.56.11)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.3.3): + /eslint-plugin-prettier@5.2.1(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.3.3): resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -2657,7 +2623,7 @@ packages: eslint-config-prettier: optional: true dependencies: - '@types/eslint': 8.56.11 + '@types/eslint': 8.56.12 eslint: 8.57.0 eslint-config-prettier: 9.1.0(eslint@8.57.0) prettier: 3.3.3 @@ -2835,7 +2801,7 @@ packages: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3160,8 +3126,8 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /inline-style-parser@0.2.3: - resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} + /inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} dev: false /internal-slot@1.0.7: @@ -3214,8 +3180,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /is-core-module@2.15.0: - resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + /is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 @@ -3397,8 +3363,8 @@ packages: '@types/react-reconciler': 0.28.8 react: 18.3.1 - /iwer@1.0.3: - resolution: {integrity: sha512-uvqE1wjPO/lBRb8+ouPbWb5hWVNM+WBO5U9rW5OkgUnBLhZndfHcGzcppt0rW5tbKbwXzaHkpLBWkKZZGWOWPA==} + /iwer@1.0.4: + resolution: {integrity: sha512-k5ABDKz28VN98nAwlZQuN62KR2ziBDOYJiFTy73rGk1UyaC01p3jdT8lVfWMF3yJTAz+872h9veuRVcUo6JXyg==} dependencies: gl-matrix: 3.4.3 dev: false @@ -3419,10 +3385,6 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - dev: true - /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -3520,14 +3482,6 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: false - /local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - dependencies: - mlly: 1.7.1 - pkg-types: 1.1.3 - dev: true - /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3557,8 +3511,8 @@ packages: dependencies: js-tokens: 4.0.0 - /loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + /loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} dependencies: get-func-name: 2.0.2 dev: true @@ -3608,23 +3562,23 @@ packages: /meshoptimizer@0.18.1: resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} - /micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} dependencies: braces: 3.0.3 picomatch: 2.3.1 - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: false - /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} dev: true + /mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + dev: false + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -3646,15 +3600,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: false - /mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - dependencies: - acorn: 8.12.1 - pathe: 1.1.2 - pkg-types: 1.1.3 - ufo: 1.5.4 - dev: true - /mmd-parser@1.0.4: resolution: {integrity: sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==} dev: false @@ -3785,13 +3730,6 @@ packages: wrappy: 1.0.2 dev: true - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: false - /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -3799,6 +3737,13 @@ packages: mimic-fn: 4.0.0 dev: true + /onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + dependencies: + mimic-function: 5.0.1 + dev: false + /opentype.js@1.3.4: resolution: {integrity: sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==} engines: {node: '>= 8.0.0'} @@ -3820,12 +3765,12 @@ packages: word-wrap: 1.2.5 dev: true - /ora@8.0.1: - resolution: {integrity: sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==} + /ora@8.1.0: + resolution: {integrity: sha512-GQEkNkH/GHOhPFXcqZs3IDahXEQcQxsSjEkK4KvEEST4t7eNzoMjxTzef+EZ+JluDEV+Raoi3WQ2CflnRdSVnQ==} engines: {node: '>=18'} dependencies: chalk: 5.3.0 - cli-cursor: 4.0.0 + cli-cursor: 5.0.0 cli-spinners: 2.9.2 is-interactive: 2.0.0 is-unicode-supported: 2.0.0 @@ -3842,13 +3787,6 @@ packages: yocto-queue: 0.1.0 dev: true - /p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - dependencies: - yocto-queue: 1.1.1 - dev: true - /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -3906,8 +3844,9 @@ packages: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} dev: true - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} dev: true /picocolors@1.0.1: @@ -3927,14 +3866,6 @@ packages: engines: {node: '>= 6'} dev: false - /pkg-types@1.1.3: - resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} - dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 - dev: true - /playwright-core@1.46.1: resolution: {integrity: sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==} engines: {node: '>=18'} @@ -4046,8 +3977,8 @@ packages: source-map-js: 1.2.0 dev: false - /postcss@8.4.41: - resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + /postcss@8.4.43: + resolution: {integrity: sha512-gJAQVYbh5R3gYm33FijzCZj7CHyQ3hWMgJMprLUlIYqCwTeZhBQ19wp0e9mA25BUbEvY5+EXuuaAjqQsrBxQBQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 @@ -4076,15 +4007,6 @@ packages: engines: {node: '>=14'} hasBin: true - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - dev: true - /promise-worker-transferable@1.0.4: resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} dependencies: @@ -4140,10 +4062,6 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - /react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - dev: true - /react-reconciler@0.27.0(react@18.3.1): resolution: {integrity: sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==} engines: {node: '>=0.10.0'} @@ -4222,7 +4140,7 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.15.0 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4230,17 +4148,17 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true dependencies: - is-core-module: 2.15.0 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true - /restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 + onetime: 7.0.0 + signal-exit: 4.1.0 dev: false /reusify@1.0.4: @@ -4255,29 +4173,29 @@ packages: glob: 7.2.3 dev: true - /rollup@4.20.0: - resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} + /rollup@4.21.2: + resolution: {integrity: sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.20.0 - '@rollup/rollup-android-arm64': 4.20.0 - '@rollup/rollup-darwin-arm64': 4.20.0 - '@rollup/rollup-darwin-x64': 4.20.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 - '@rollup/rollup-linux-arm-musleabihf': 4.20.0 - '@rollup/rollup-linux-arm64-gnu': 4.20.0 - '@rollup/rollup-linux-arm64-musl': 4.20.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 - '@rollup/rollup-linux-riscv64-gnu': 4.20.0 - '@rollup/rollup-linux-s390x-gnu': 4.20.0 - '@rollup/rollup-linux-x64-gnu': 4.20.0 - '@rollup/rollup-linux-x64-musl': 4.20.0 - '@rollup/rollup-win32-arm64-msvc': 4.20.0 - '@rollup/rollup-win32-ia32-msvc': 4.20.0 - '@rollup/rollup-win32-x64-msvc': 4.20.0 + '@rollup/rollup-android-arm-eabi': 4.21.2 + '@rollup/rollup-android-arm64': 4.21.2 + '@rollup/rollup-darwin-arm64': 4.21.2 + '@rollup/rollup-darwin-x64': 4.21.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.2 + '@rollup/rollup-linux-arm-musleabihf': 4.21.2 + '@rollup/rollup-linux-arm64-gnu': 4.21.2 + '@rollup/rollup-linux-arm64-musl': 4.21.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.2 + '@rollup/rollup-linux-riscv64-gnu': 4.21.2 + '@rollup/rollup-linux-s390x-gnu': 4.21.2 + '@rollup/rollup-linux-x64-gnu': 4.21.2 + '@rollup/rollup-linux-x64-musl': 4.21.2 + '@rollup/rollup-win32-arm64-msvc': 4.21.2 + '@rollup/rollup-win32-ia32-msvc': 4.21.2 + '@rollup/rollup-win32-x64-msvc': 4.21.2 fsevents: 2.3.3 dev: true @@ -4376,10 +4294,6 @@ packages: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: false - /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -4442,7 +4356,7 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} dependencies: - emoji-regex: 10.3.0 + emoji-regex: 10.4.0 get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 dev: false @@ -4531,14 +4445,8 @@ packages: engines: {node: '>=8'} dev: true - /strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - dependencies: - js-tokens: 9.0.0 - dev: true - - /styled-components@6.1.12(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==} + /styled-components@6.1.13(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==} engines: {node: '>= 16'} peerDependencies: react: '>= 16.8.0' @@ -4605,7 +4513,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.1.1 - tslib: 2.6.3 + tslib: 2.7.0 dev: true /tailwindcss@3.3.2: @@ -4623,7 +4531,7 @@ packages: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 @@ -4672,7 +4580,7 @@ packages: dependencies: '@types/draco3d': 1.4.10 '@types/offscreencanvas': 2019.7.3 - '@types/webxr': 0.5.19 + '@types/webxr': 0.5.20 chevrotain: 10.5.0 draco3d: 1.5.7 fflate: 0.6.10 @@ -4691,7 +4599,7 @@ packages: dependencies: '@types/draco3d': 1.4.10 '@types/offscreencanvas': 2019.7.3 - '@types/webxr': 0.5.19 + '@types/webxr': 0.5.20 draco3d: 1.5.7 fflate: 0.6.10 potpack: 1.0.2 @@ -4717,13 +4625,18 @@ packages: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} dev: true - /tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + /tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + dev: true + + /tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} dev: true - /tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + /tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} engines: {node: '>=14.0.0'} dev: true @@ -4788,14 +4701,14 @@ packages: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false - /tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + /tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} dev: true - /tunnel-rat@0.1.2(@types/react@18.3.3)(react@18.3.1): + /tunnel-rat@0.1.2(@types/react@18.3.5)(react@18.3.1): resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} dependencies: - zustand: 4.5.5(@types/react@18.3.3)(react@18.3.1) + zustand: 4.5.5(@types/react@18.3.5)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer @@ -4820,11 +4733,6 @@ packages: prelude-ls: 1.2.1 dev: true - /type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} - dev: true - /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -4879,10 +4787,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - dev: true - /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -4892,8 +4796,8 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /undici-types@6.13.0: - resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + /undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} dev: true /update-browserslist-db@1.1.0(browserslist@4.23.3): @@ -4903,7 +4807,7 @@ packages: browserslist: '>= 4.21.0' dependencies: browserslist: 4.23.3 - escalade: 3.1.2 + escalade: 3.2.0 picocolors: 1.0.1 dev: true @@ -4943,7 +4847,7 @@ packages: hasBin: true dev: false - /valtio@1.13.2(@types/react@18.3.3)(react@18.3.1): + /valtio@1.13.2(@types/react@18.3.5)(react@18.3.1): resolution: {integrity: sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==} engines: {node: '>=12.20.0'} peerDependencies: @@ -4955,23 +4859,23 @@ packages: react: optional: true dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.5 derive-valtio: 0.1.0(valtio@1.13.2) proxy-compare: 2.6.0 react: 18.3.1 use-sync-external-store: 1.2.0(react@18.3.1) dev: false - /vite-node@1.6.0(@types/node@20.15.0): - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + /vite-node@2.0.5(@types/node@20.16.3): + resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.6 pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.4.1(@types/node@20.15.0) + tinyrainbow: 1.2.0 + vite: 5.4.2(@types/node@20.16.3) transitivePeerDependencies: - '@types/node' - less @@ -4984,8 +4888,8 @@ packages: - terser dev: true - /vite@5.4.1(@types/node@20.15.0): - resolution: {integrity: sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==} + /vite@5.4.2(@types/node@20.16.3): + resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5015,23 +4919,23 @@ packages: terser: optional: true dependencies: - '@types/node': 20.15.0 + '@types/node': 20.16.3 esbuild: 0.21.5 - postcss: 8.4.41 - rollup: 4.20.0 + postcss: 8.4.43 + rollup: 4.21.2 optionalDependencies: fsevents: 2.3.3 dev: true - /vitest@1.6.0(@types/node@20.15.0): - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + /vitest@2.0.5(@types/node@20.16.3): + resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@vitest/browser': 2.0.5 + '@vitest/ui': 2.0.5 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5048,26 +4952,25 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.15.0 - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.3 - chai: 4.5.0 + '@ampproject/remapping': 2.3.0 + '@types/node': 20.16.3 + '@vitest/expect': 2.0.5 + '@vitest/pretty-format': 2.0.5 + '@vitest/runner': 2.0.5 + '@vitest/snapshot': 2.0.5 + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 debug: 4.3.6 execa: 8.0.1 - local-pkg: 0.5.0 magic-string: 0.30.11 pathe: 1.1.2 - picocolors: 1.0.1 std-env: 3.7.0 - strip-literal: 2.1.0 tinybench: 2.9.0 - tinypool: 0.8.4 - vite: 5.4.1(@types/node@20.15.0) - vite-node: 1.6.0(@types/node@20.15.0) + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.2(@types/node@20.16.3) + vite-node: 2.0.5(@types/node@20.16.3) why-is-node-running: 2.3.0 transitivePeerDependencies: - less @@ -5195,11 +5098,6 @@ packages: engines: {node: '>=10'} dev: true - /yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} - engines: {node: '>=12.20'} - dev: true - /yoga-layout@3.1.0: resolution: {integrity: sha512-auzJ8lEovThZIpR8wLGWNo/JEj4VTO79q9/gOJ0dWb3shAYPFdX3t9VN0fC0v+jeQF77STUdCzebLwRMqzn5gQ==} dev: false @@ -5223,7 +5121,7 @@ packages: dependencies: react: 18.3.1 - /zustand@4.5.5(@types/react@18.3.3)(react@18.3.1): + /zustand@4.5.5(@types/react@18.3.5)(react@18.3.1): resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} engines: {node: '>=12.7.0'} peerDependencies: @@ -5238,7 +5136,7 @@ packages: react: optional: true dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.5 react: 18.3.1 use-sync-external-store: 1.2.2(react@18.3.1) dev: false