diff --git a/README.md b/README.md index 51ce446..088e480 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,10 @@ Controllers can be added with `` for [motion-controllers](https:/ /> ``` +### Environment map + +You can set environment map and/or it's intensity on controller models via props on ``. See [ControllerEnvMap](./examples/src/demos/ControllersEnvMap.tsx) to find out how to do it. + ### useController `useController` references an `XRController` by handedness, exposing position and orientation info. @@ -172,14 +176,15 @@ const gazeController = useController('none') ### XRController -`XRController` is an `Object3D` that represents an [`XRInputSource`](https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource) with the following properties: +`XRController` is an long-living `Object3D` that represents an [`XRInputSource`](https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource) with the following properties: ```jsx index: number controller: THREE.XRTargetRaySpace grip: THREE.XRGripSpace hand: THREE.XRHandSpace -inputSource: XRInputSource +inputSource: XRInputSource | null +xrControllerModel: XRControllerModel | null ``` ## Interactions diff --git a/examples/src/assets/brown_photostudio_04_256.hdr b/examples/src/assets/brown_photostudio_04_256.hdr new file mode 100644 index 0000000..8342846 Binary files /dev/null and b/examples/src/assets/brown_photostudio_04_256.hdr differ diff --git a/examples/src/demos/ControllersEnvMap.tsx b/examples/src/demos/ControllersEnvMap.tsx new file mode 100644 index 0000000..34a10e3 --- /dev/null +++ b/examples/src/demos/ControllersEnvMap.tsx @@ -0,0 +1,41 @@ +import { Canvas, useThree } from '@react-three/fiber' +import { XR, VRButton, Controllers } from '@react-three/xr' +import { PMREMGenerator, Texture } from 'three' +import { RGBELoader } from 'three-stdlib' +import { useEffect, useState } from 'react' +import EnvMap from '../assets/brown_photostudio_04_256.hdr' + +function ControllersWithEnvMap() { + const renderer = useThree(({ gl }) => gl) + const [envMap, setEnvMap] = useState() + + useEffect(() => { + const generateEnvMap = async () => { + const rgbeLoader = new RGBELoader() + const dataTexture = await rgbeLoader.loadAsync(EnvMap) + const pmremGenerator = new PMREMGenerator(renderer) + pmremGenerator.compileEquirectangularShader() + const rt = pmremGenerator.fromEquirectangular(dataTexture) + const radianceMap = rt.texture + setEnvMap(radianceMap) + pmremGenerator.dispose() + } + + generateEnvMap() + }, [renderer]) + + return +} + +export default function () { + return ( + <> + console.error(e)} /> + + + + + + + ) +} diff --git a/examples/src/demos/index.tsx b/examples/src/demos/index.tsx index 5f0613e..7855f4f 100644 --- a/examples/src/demos/index.tsx +++ b/examples/src/demos/index.tsx @@ -5,7 +5,8 @@ const HitTest = { Component: lazy(() => import('./HitTest')) } const Player = { Component: lazy(() => import('./Player')) } const Text = { Component: lazy(() => import('./Text')) } const Hands = { Component: lazy(() => import('./Hands')) } +const ControllersEnvMap = { Component: lazy(() => import('./ControllersEnvMap')) } const Teleport = { Component: lazy(() => import('./Teleport')) } const CameraLinkedObject = { Component: lazy(() => import('./CameraLinkedObject')) } -export { Interactive, HitTest, Player, Text, Hands, Teleport, CameraLinkedObject } +export { Interactive, HitTest, Player, Text, Hands, Teleport, CameraLinkedObject, ControllersEnvMap } diff --git a/examples/src/global.d.ts b/examples/src/global.d.ts new file mode 100644 index 0000000..6804e0f --- /dev/null +++ b/examples/src/global.d.ts @@ -0,0 +1,4 @@ +declare module '*.hdr' { + const path: string + export = path +} \ No newline at end of file diff --git a/examples/yarn.lock b/examples/yarn.lock index 4f17e9e..4d6940a 100644 --- a/examples/yarn.lock +++ b/examples/yarn.lock @@ -634,10 +634,10 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@mediapipe/tasks-vision@0.10.2-rc2": - version "0.10.2-rc2" - resolved "https://registry.yarnpkg.com/@mediapipe/tasks-vision/-/tasks-vision-0.10.2-rc2.tgz#e3fa5d84d58b9031a0e975d1e5ef8eb8e4a6fc11" - integrity sha512-b9ar6TEUo8I07n/jXSuKDu5HgzkDah9pe4H8BYpcubhCEahlfDD5ixE+9SQyJM4HXHXdF9nN/wRQT7rEnLz7Gg== +"@mediapipe/tasks-vision@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@mediapipe/tasks-vision/-/tasks-vision-0.10.1.tgz#68047459352019cc141dc9c1d15c05b8ab689423" + integrity sha512-/zIKjOAIABx+KVfqe8hA6X2pxBGsBYlEtvD7/gpXecvzKefo/JQO6XaggmJul7+noaqiPYM0CVGZxmFJ2oTdSQ== "@nicolo-ribaudo/semver-v6@^6.3.3": version "6.3.3" @@ -718,9 +718,9 @@ zustand "^3.5.13" "@react-three/fiber@^8.10.0": - version "8.13.4" - resolved "https://registry.yarnpkg.com/@react-three/fiber/-/fiber-8.13.4.tgz#27cf964bd1d353884fb9555e21b0460736d173b5" - integrity sha512-OmyRKt9JU2i/Rc3uw4A+zERXKkFdu8slJjWQZfacoFNHIzGP9QVQ9XxlJWgTbgTLIOD39cUgnmH3RZZGWJqAoQ== + version "8.13.7" + resolved "https://registry.yarnpkg.com/@react-three/fiber/-/fiber-8.13.7.tgz#809f63c85effc7dddd3001ee10c2256c53a82b16" + integrity sha512-fH1wYi8+A2YZX8uYd9N4hfbAV+kHE565s7f62+SMNmpeynaUsN8NzXACmmJ6BpVKAKdxfvOde6dBGwG1BrWOKQ== dependencies: "@babel/runtime" "^7.17.8" "@types/react-reconciler" "^0.26.7" @@ -930,10 +930,10 @@ cac@^6.7.14: resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -camera-controls@^2.4.2: - version "2.7.0" - resolved "https://registry.yarnpkg.com/camera-controls/-/camera-controls-2.7.0.tgz#13e2895375fbd8fb3353baeada6c8bc267a60d09" - integrity sha512-HONMoMYHieOCQOoweS639bdWHP/P/fvVGR08imnECGVUp04mqGfsX/zp1ZufLeiAA5hA6i1JhP6SrnOwh01C0w== +camera-controls@^2.3.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/camera-controls/-/camera-controls-2.4.2.tgz#815aa5d7c4c43054fc55fb8b6cc685a56540fea2" + integrity sha512-blYDPECYFT/4egDMNWqKc2lBrpOfIAjPPRUNVswQELPi8naGBXUvZM3sDJSNuIRaHqid+JKPtlcoZk+Cb+X5qg== caniuse-lite@^1.0.30001503: version "1.0.30001512" @@ -1035,10 +1035,10 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -detect-gpu@^5.0.28: - version "5.0.31" - resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.31.tgz#5749bea3aa56bc2ec41383b585f6cbd965619fee" - integrity sha512-+ZZr/deA5OvuBxod6kKFUvpZA9YR2r4fRYlAJGL7N5aUSLrY3Xgi+K4U5NHmeuk2mNC044n1YJwsq2Aw6hPmUw== +detect-gpu@^5.0.14: + version "5.0.27" + resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.27.tgz#821d9331c87e32568c483d85e12a9adee43d7bb2" + integrity sha512-IDjjqTkS+f0xm/ntbD21IPYiF0srzpePC/hhUMmctEsoklZwJwStJiMi/KN0pnH0LjSsgjwbP+QwW7y+Qf4/SQ== dependencies: webgl-constants "^1.1.1" @@ -1251,10 +1251,10 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -maath@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/maath/-/maath-0.6.0.tgz#7841d0fb95bbb37d19b08b7c5458ef70190950d2" - integrity sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw== +maath@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/maath/-/maath-0.5.3.tgz#777a1f9b8463c6ffb199ea43406874a357c0cd58" + integrity sha512-ut63A4zTd9abtpi+sOHW1fPWPtAFrjK0E17eAthx1k93W/T2cWLKV5oaswyotJVDvvW1EXSdokAqhK5KOu0Qdw== magic-string@^0.27.0: version "0.27.0" @@ -1532,20 +1532,25 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +suspend-react@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/suspend-react/-/suspend-react-0.0.8.tgz#b0740c1386b4eb652f17affe4339915ee268bd31" + integrity sha512-ZC3r8Hu1y0dIThzsGw0RLZplnX9yXwfItcvaIzJc2VQVi8TGyGDlu92syMB5ulybfvGLHAI5Ghzlk23UBPF8xg== + suspend-react@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/suspend-react/-/suspend-react-0.1.3.tgz#a52f49d21cfae9a2fb70bd0c68413d3f9d90768e" integrity sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ== -three-mesh-bvh@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.6.0.tgz#15523c335383df658dc60063a783fdd52d045dc5" - integrity sha512-4/oXeqVMLuN9/P0M3L5ezIVrFiXQXKvjVTErkiSYMjSaPoWfNPAwqulSgLf4bIUPn8/Lq3rmIJwxbCuD8qDobA== +three-mesh-bvh@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.5.23.tgz#08e5b629144b48b11acbd433519680e457d398ed" + integrity sha512-nyk+MskdyDgECqkxdv57UjazqqhrMi+Al9PxJN6yFtx1CTW4r0eCQ27FtyYKY5gCIWhxjtNfWYDPVy8lzx6LkA== -three-stdlib@^2.23.9: - version "2.23.12" - resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.23.12.tgz#f269398e3125c77bcd374d87f4c1da8d550e7f21" - integrity sha512-YFpuCu/ZVHBiK42bzEihZTA3tvEPQhaKE5tYej41AlNYXbwIWxO93fxYYrX7vs275s0yCKr6Zp6y7kI+mOklRQ== +three-stdlib@^2.23.5: + version "2.23.9" + resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.23.9.tgz#09c74fc6acced3d124e4f9d695156136c587a355" + integrity sha512-fYBClVGQptD7UZcoRZGNlR3sKcUW37hVPoEW1v68E4XuiwD0Ml/VqDUJ0yEMVE2DlooDvqgqv/rIcHC/B4N5pg== dependencies: "@types/draco3d" "^1.4.0" "@types/offscreencanvas" "^2019.6.4" @@ -1574,7 +1579,7 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== -troika-three-text@^0.47.2: +troika-three-text@^0.47.1: version "0.47.2" resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.47.2.tgz#fdf89059c010563bb829262b20c41f69ca79b712" integrity sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng== diff --git a/package.json b/package.json index 6737bd7..5130bc9 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "three": "^0.141.0", "typescript": "^4.5.5", "vite": "^3.0.5", - "vitest": "^0.29.1" + "vitest": "^0.34.3" }, "dependencies": { "@types/webxr": "*", diff --git a/src/Controllers.test.tsx b/src/Controllers.test.tsx index c567ecf..25c60be 100644 --- a/src/Controllers.test.tsx +++ b/src/Controllers.test.tsx @@ -8,6 +8,7 @@ import { XRControllerModel } from './XRControllerModel' import { XRControllerModelFactoryMock } from './mocks/XRControllerModelFactoryMock' import { XRInputSourceMock } from './mocks/XRInputSourceMock' import { act } from '@react-three/test-renderer' +import { Texture } from 'three' vi.mock('./XRControllerModelFactory', async () => { const { XRControllerModelFactoryMock } = await vi.importActual( @@ -141,4 +142,86 @@ describe('Controllers', () => { expect(disconnectSpy).not.toBeCalled() expect(xrControllerModelFactory?.initializeControllerModel).toBeCalledTimes(1) }) + + describe('envMap', () => { + it("should not set env map if it's not provided in props", async () => { + const store = createStoreMock() + const xrControllerMock = new XRControllerMock(0) + store.setState({ controllers: [xrControllerMock] }) + + await render(, { wrapper: createStoreProvider(store) }) + + const xrControllerModel = xrControllerMock.xrControllerModel + + expect(xrControllerModel!.envMap).toBeNull() + expect(xrControllerModel!.envMapIntensity).toBe(1) + }) + + it("should set env map if it's provided in props", async () => { + const store = createStoreMock() + const xrControllerMock = new XRControllerMock(0) + store.setState({ controllers: [xrControllerMock] }) + const envMap = new Texture() + + await render(, { wrapper: createStoreProvider(store) }) + + const xrControllerModel = xrControllerMock.xrControllerModel + + expect(xrControllerModel!.envMap).toBe(envMap) + expect(xrControllerModel!.envMapIntensity).toBe(1) + }) + + it("should only set env map intensity if it's provided in props", async () => { + const store = createStoreMock() + const xrControllerMock = new XRControllerMock(0) + store.setState({ controllers: [xrControllerMock] }) + + await render(, { wrapper: createStoreProvider(store) }) + + const xrControllerModel = xrControllerMock.xrControllerModel + + expect(xrControllerModel!.envMap).toBeNull() + expect(xrControllerModel!.envMapIntensity).toBe(0.5) + }) + + it("should change env map intensity if it's provided in props then updated to a different value", async () => { + const store = createStoreMock() + const xrControllerMock = new XRControllerMock(0) + store.setState({ controllers: [xrControllerMock] }) + + const { rerender } = await render(, { wrapper: createStoreProvider(store) }) + const xrControllerModel = xrControllerMock.xrControllerModel + await rerender() + + expect(xrControllerModel!.envMap).toBeNull() + expect(xrControllerModel!.envMapIntensity).toBe(0.6) + }) + + it("should remove env map if it's provided in props first, and then removed", async () => { + const store = createStoreMock() + const xrControllerMock = new XRControllerMock(0) + store.setState({ controllers: [xrControllerMock] }) + const envMap = new Texture() + + const { rerender } = await render(, { wrapper: createStoreProvider(store) }) + const xrControllerModel = xrControllerMock.xrControllerModel + await rerender() + + expect(xrControllerModel!.envMap).toBeNull() + }) + + it("should change env map intensity if it's provided in props then updated to a different value but envMap stays the same", async () => { + const store = createStoreMock() + const xrControllerMock = new XRControllerMock(0) + store.setState({ controllers: [xrControllerMock] }) + const envMap = new Texture() + + const { rerender } = await render(, { wrapper: createStoreProvider(store) }) + const xrControllerModel = xrControllerMock.xrControllerModel + await rerender() + + expect(xrControllerModel!.envMap).toBe(envMap) + expect(xrControllerModel!.envMapIntensity).toBe(0.6) + }) + }) }) diff --git a/src/Controllers.tsx b/src/Controllers.tsx index a0a3027..74a7164 100644 --- a/src/Controllers.tsx +++ b/src/Controllers.tsx @@ -4,8 +4,8 @@ import { useFrame, Object3DNode, extend, createPortal } from '@react-three/fiber import { useXR } from './XR' import { XRController } from './XRController' import { XRControllerModelFactory } from './XRControllerModelFactory' -import { useCallback } from 'react' import { XRControllerModel } from './XRControllerModel' +import { useCallbackRef } from './utils' export interface RayProps extends Partial { /** The XRController to attach the ray to */ @@ -64,16 +64,51 @@ export interface ControllersProps { rayMaterial?: JSX.IntrinsicElements['meshBasicMaterial'] /** Whether to hide controllers' rays on blur. Default is `false` */ hideRaysOnBlur?: boolean + /** + * Optional environment map to apply to controllers' models + * Useful for make controllers look more realistic + * if you don't want to apply env map globally on a scene + */ + envMap?: THREE.Texture + /** + * Optional environment map intensity to apply to controllers' models + * Useful for tweaking the env map intensity if they look too bright or too dark + */ + envMapIntensity?: number } -const ControllerModel = ({ target }: { target: XRController }) => { - const handleControllerModel = useCallback( +const ControllerModel = ({ + target, + envMap, + envMapIntensity +}: { + target: XRController + envMap?: THREE.Texture + envMapIntensity?: number +}) => { + const xrControllerModelRef = React.useRef(null) + const setEnvironmentMapRef = useCallbackRef((xrControllerModel: XRControllerModel) => { + if (envMap == null) return + xrControllerModel.setEnvironmentMap(envMap ?? null) + }) + const clearEnvironmentMapRef = useCallbackRef((xrControllerModel: XRControllerModel) => xrControllerModel.setEnvironmentMap(null)) + + const setEnvironmentMapIntensityRef = useCallbackRef((xrControllerModel: XRControllerModel) => { + if (envMapIntensity == null) return + xrControllerModel.setEnvironmentMapIntensity(envMapIntensity) + }) + + const handleControllerModel = React.useCallback( (xrControllerModel: XRControllerModel | null) => { + xrControllerModelRef.current = xrControllerModel if (xrControllerModel) { target.xrControllerModel = xrControllerModel if (target.inputSource?.hand) { return } + + setEnvironmentMapRef.current(xrControllerModel) + setEnvironmentMapIntensityRef.current(xrControllerModel) if (target.inputSource) { modelFactory.initializeControllerModel(xrControllerModel, target.inputSource) } else { @@ -87,13 +122,29 @@ const ControllerModel = ({ target }: { target: XRController }) => { target.xrControllerModel = null } }, - [target] + [target, setEnvironmentMapIntensityRef, setEnvironmentMapRef] ) + React.useLayoutEffect(() => { + if (xrControllerModelRef.current) { + if (envMap) { + setEnvironmentMapRef.current(xrControllerModelRef.current) + } else { + clearEnvironmentMapRef.current(xrControllerModelRef.current) + } + } + }, [envMap, setEnvironmentMapRef, clearEnvironmentMapRef]) + + React.useLayoutEffect(() => { + if (xrControllerModelRef.current) { + setEnvironmentMapIntensityRef.current(xrControllerModelRef.current) + } + }, [envMapIntensity, setEnvironmentMapIntensityRef]) + return } -export function Controllers({ rayMaterial = {}, hideRaysOnBlur = false }: ControllersProps) { +export function Controllers({ rayMaterial = {}, hideRaysOnBlur = false, envMap, envMapIntensity }: ControllersProps) { const controllers = useXR((state) => state.controllers) const isHandTracking = useXR((state) => state.isHandTracking) const rayMaterialProps = React.useMemo( @@ -113,7 +164,7 @@ export function Controllers({ rayMaterial = {}, hideRaysOnBlur = false }: Contro <> {controllers.map((target, i) => ( - {createPortal(, target.grip)} + {createPortal(, target.grip)} {createPortal( , target.controller diff --git a/src/XRControllerModel.test.ts b/src/XRControllerModel.test.ts index f92ac95..6f7ba40 100644 --- a/src/XRControllerModel.test.ts +++ b/src/XRControllerModel.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from 'vitest' import { XRControllerModel } from './XRControllerModel' -import { Object3D, Texture } from 'three' +import { BoxBufferGeometry, Mesh, MeshStandardMaterial, Object3D, Texture } from 'three' import { MotionControllerMock } from './mocks/MotionControllerMock' describe('XRControllerModel', () => { @@ -35,27 +35,147 @@ describe('XRControllerModel', () => { expect(xrControllerModel.children).toContain(sceneMock) }) - it('should set and apply environment map when setEnvironment map is called', () => { + it('should update motioncontroller from gamepad on updateMatrixWorld', () => { const xrControllerModel = new XRControllerModel() const motionControllerMock = new MotionControllerMock() - const sceneMock = new Object3D() - const envMapMock = new Texture() xrControllerModel.connectMotionController(motionControllerMock) - xrControllerModel.connectModel(sceneMock) - xrControllerModel.setEnvironmentMap(envMapMock, 0.5) + xrControllerModel.updateMatrixWorld(false) - expect(xrControllerModel.envMap).toBe(envMapMock) - expect(xrControllerModel.envMapIntensity).toBe(0.5) + expect(xrControllerModel.motionController?.updateFromGamepad).toBeCalled() }) - it('should update motioncontroller from gamepad on updateMatrixWorld', () => { - const xrControllerModel = new XRControllerModel() - const motionControllerMock = new MotionControllerMock() + describe('envMap', () => { + it('should set and apply environment map when setEnvironmentMap is called after scene is loaded', () => { + const xrControllerModel = new XRControllerModel() + const motionControllerMock = new MotionControllerMock() + const sceneMock = new Object3D() + const mesh = new Mesh(new BoxBufferGeometry(), new MeshStandardMaterial()) + sceneMock.add(mesh) + const materialNeedsUpdateSpy = vi.spyOn(mesh.material, 'needsUpdate', 'set') + const envMapMock = new Texture() - xrControllerModel.connectMotionController(motionControllerMock) - xrControllerModel.updateMatrixWorld(false) + xrControllerModel.connectMotionController(motionControllerMock) + xrControllerModel.connectModel(sceneMock) + xrControllerModel.setEnvironmentMap(envMapMock, 0.5) - expect(xrControllerModel.motionController?.updateFromGamepad).toBeCalled() + expect(xrControllerModel.envMap).toBe(envMapMock) + expect(xrControllerModel.envMapIntensity).toBe(0.5) + + expect(mesh.material.envMap).toBe(envMapMock) + expect(mesh.material.envMapIntensity).toBe(0.5) + expect(materialNeedsUpdateSpy).toBeCalledWith(true) + }) + + it('should set and apply environment map when setEnvironmentMap is called after scene is loaded', () => { + const xrControllerModel = new XRControllerModel() + const motionControllerMock = new MotionControllerMock() + const sceneMock = new Object3D() + const mesh = new Mesh(new BoxBufferGeometry(), new MeshStandardMaterial()) + sceneMock.add(mesh) + const materialNeedsUpdateSpy = vi.spyOn(mesh.material, 'needsUpdate', 'set') + const envMapMock = new Texture() + + xrControllerModel.connectMotionController(motionControllerMock) + xrControllerModel.connectModel(sceneMock) + xrControllerModel.setEnvironmentMap(envMapMock) + + expect(xrControllerModel.envMap).toBe(envMapMock) + expect(xrControllerModel.envMapIntensity).toBe(1) + + expect(mesh.material.envMap).toBe(envMapMock) + expect(mesh.material.envMapIntensity).toBe(1) + expect(materialNeedsUpdateSpy).toBeCalledWith(true) + }) + + it('should set and apply environment map to an array material when setEnvironment map is called', () => { + const xrControllerModel = new XRControllerModel() + const motionControllerMock = new MotionControllerMock() + const sceneMock = new Object3D() + const material1 = new MeshStandardMaterial() + const material2 = new MeshStandardMaterial() + const mesh = new Mesh(new BoxBufferGeometry(), [material1, material2]) + sceneMock.add(mesh) + const material1NeedsUpdateSpy = vi.spyOn(material1, 'needsUpdate', 'set') + const material2NeedsUpdateSpy = vi.spyOn(material2, 'needsUpdate', 'set') + const envMapMock = new Texture() + + xrControllerModel.connectMotionController(motionControllerMock) + xrControllerModel.connectModel(sceneMock) + xrControllerModel.setEnvironmentMap(envMapMock, 0.5) + + expect(xrControllerModel.envMap).toBe(envMapMock) + expect(xrControllerModel.envMapIntensity).toBe(0.5) + + expect(material1.envMap).toBe(envMapMock) + expect(material1.envMapIntensity).toBe(0.5) + expect(material1NeedsUpdateSpy).toBeCalledWith(true) + + expect(material2.envMap).toBe(envMapMock) + expect(material2.envMapIntensity).toBe(0.5) + expect(material2NeedsUpdateSpy).toBeCalledWith(true) + }) + + it('should set and apply environment map when setEnvironment map is called before scene is loaded', () => { + const xrControllerModel = new XRControllerModel() + const motionControllerMock = new MotionControllerMock() + const sceneMock = new Object3D() + const mesh = new Mesh(new BoxBufferGeometry(), new MeshStandardMaterial()) + sceneMock.add(mesh) + const materialNeedsUpdateSpy = vi.spyOn(mesh.material, 'needsUpdate', 'set') + const envMapMock = new Texture() + + xrControllerModel.connectMotionController(motionControllerMock) + xrControllerModel.setEnvironmentMap(envMapMock, 0.5) + xrControllerModel.connectModel(sceneMock) + + expect(xrControllerModel.envMap).toBe(envMapMock) + expect(xrControllerModel.envMapIntensity).toBe(0.5) + + expect(mesh.material.envMap).toBe(envMapMock) + expect(mesh.material.envMapIntensity).toBe(0.5) + expect(materialNeedsUpdateSpy).toBeCalledWith(true) + }) + + it('should set environment map intensity when setEnvironmentMapIntensity is called before scene is loaded', () => { + const xrControllerModel = new XRControllerModel() + const motionControllerMock = new MotionControllerMock() + const sceneMock = new Object3D() + const mesh = new Mesh(new BoxBufferGeometry(), new MeshStandardMaterial()) + sceneMock.add(mesh) + const materialNeedsUpdateSpy = vi.spyOn(mesh.material, 'needsUpdate', 'set') + + xrControllerModel.connectMotionController(motionControllerMock) + xrControllerModel.setEnvironmentMapIntensity(0.5) + xrControllerModel.connectModel(sceneMock) + + expect(xrControllerModel.envMap).toBe(null) + expect(xrControllerModel.envMapIntensity).toBe(0.5) + + expect(mesh.material.envMap).toBe(null) + expect(mesh.material.envMapIntensity).toBe(0.5) + expect(materialNeedsUpdateSpy).toBeCalledWith(true) + }) + + it('should remove environment map when setEnvironmentMap is called with null', () => { + const xrControllerModel = new XRControllerModel() + const motionControllerMock = new MotionControllerMock() + const sceneMock = new Object3D() + const mesh = new Mesh(new BoxBufferGeometry(), new MeshStandardMaterial()) + sceneMock.add(mesh) + const materialNeedsUpdateSpy = vi.spyOn(mesh.material, 'needsUpdate', 'set') + const envMapMock = new Texture() + + xrControllerModel.connectMotionController(motionControllerMock) + xrControllerModel.connectModel(sceneMock) + xrControllerModel.setEnvironmentMap(envMapMock) + expect.soft(xrControllerModel.envMap).toBe(envMapMock) + + xrControllerModel.setEnvironmentMap(null) + + expect(xrControllerModel.envMap).toBe(null) + expect(mesh.material.envMap).toBe(null) + expect(materialNeedsUpdateSpy).toBeCalledWith(true) + }) }) }) diff --git a/src/XRControllerModel.ts b/src/XRControllerModel.ts index 0051499..3701135 100644 --- a/src/XRControllerModel.ts +++ b/src/XRControllerModel.ts @@ -7,22 +7,47 @@ import { MeshLambertMaterial, MeshPhongMaterial, MeshStandardMaterial, - SphereGeometry + SphereGeometry, + Material } from 'three' import { MotionController, MotionControllerConstants } from 'three-stdlib' -const isEnvMapApplicable = ( - material: any -): material is MeshBasicMaterial | MeshStandardMaterial | MeshPhongMaterial | MeshLambertMaterial => 'envMap' in material +type MaterialsWithEnvMap = MeshBasicMaterial | MeshStandardMaterial | MeshPhongMaterial | MeshLambertMaterial -const applyEnvironmentMap = (envMap: Texture, envMapIntensity: number, obj: Object3D): void => { - obj.traverse((child) => { - if (child instanceof Mesh && isEnvMapApplicable(child.material)) { - child.material.envMap = envMap - if ('envMapIntensity' in child.material) child.material.envMapIntensity = envMapIntensity - child.material.needsUpdate = true +const isEnvMapApplicable = (material: Material): material is MaterialsWithEnvMap => 'envMap' in material + +const updateEnvMap = (material: MaterialsWithEnvMap, envMap: Texture | null) => { + material.envMap = envMap + material.needsUpdate = true +} + +const applyEnvironmentMap = (envMap: Texture | null, obj: Object3D): void => { + if (obj instanceof Mesh) { + if (Array.isArray(obj.material)) { + obj.material.forEach((m) => (isEnvMapApplicable(m) ? updateEnvMap(m, envMap) : undefined)) + } else if (isEnvMapApplicable(obj.material)) { + updateEnvMap(obj.material, envMap) } - }) + } +} + +type MaterialsWithEnvMapIntensity = Material & { envMapIntensity: any } + +const isEnvMapIntensityApplicable = (material: Material): material is MaterialsWithEnvMapIntensity => 'envMapIntensity' in material + +const updateEnvMapIntensity = (material: MaterialsWithEnvMapIntensity, envMapIntensity: number) => { + material.envMapIntensity = envMapIntensity + material.needsUpdate = true +} + +const applyEnvironmentMapIntensity = (envMapIntensity: number, obj: Object3D): void => { + if (obj instanceof Mesh) { + if (Array.isArray(obj.material)) { + obj.material.forEach((m) => (isEnvMapIntensityApplicable(m) ? updateEnvMapIntensity(m, envMapIntensity) : undefined)) + } else if (isEnvMapIntensityApplicable(obj.material)) { + updateEnvMapIntensity(obj.material, envMapIntensity) + } + } } /** @@ -83,8 +108,11 @@ function addAssetSceneToControllerModel(controllerModel: XRControllerModel, scen findNodes(controllerModel.motionController!, scene) // Apply any environment map that the mesh already has set. - if (controllerModel.envMap) { - applyEnvironmentMap(controllerModel.envMap, controllerModel.envMapIntensity, scene) + if (controllerModel.envMap || controllerModel.envMapIntensity != null) { + scene.traverse((c) => { + if (controllerModel.envMap) applyEnvironmentMap(controllerModel.envMap, c) + if (controllerModel.envMapIntensity != null) applyEnvironmentMapIntensity(controllerModel.envMapIntensity, c) + }) } // Add the glTF scene to the controllerModel. @@ -106,14 +134,28 @@ export class XRControllerModel extends Group { this.scene = null } - setEnvironmentMap(envMap: Texture, envMapIntensity = 1): XRControllerModel { + setEnvironmentMap(envMap: Texture | null, envMapIntensity = 1): XRControllerModel { if (this.envMap === envMap && this.envMapIntensity === envMapIntensity) { return this } this.envMap = envMap this.envMapIntensity = envMapIntensity - applyEnvironmentMap(envMap, envMapIntensity, this) + this.scene?.traverse((c) => { + applyEnvironmentMap(envMap, c) + applyEnvironmentMapIntensity(envMapIntensity, c) + }) + + return this + } + + setEnvironmentMapIntensity(envMapIntensity: number): XRControllerModel { + if (this.envMapIntensity === envMapIntensity) { + return this + } + + this.envMapIntensity = envMapIntensity + this.scene?.traverse((c) => applyEnvironmentMapIntensity(envMapIntensity, c)) return this } diff --git a/src/mocks/XRControllerModelMock.ts b/src/mocks/XRControllerModelMock.ts index e555457..364fee5 100644 --- a/src/mocks/XRControllerModelMock.ts +++ b/src/mocks/XRControllerModelMock.ts @@ -8,7 +8,10 @@ export class XRControllerModelMock extends Group implements XRControllerModel { envMapIntensity = 1 motionController: MotionController | null = null scene: Object3D | null = null - setEnvironmentMap(_envMap: Texture, _envMapIntensity?: number): XRControllerModel { + setEnvironmentMap(_envMap: Texture): XRControllerModel { + throw new Error('Method not implemented.') + } + setEnvironmentMapIntensity( _envMapIntensity?: number): XRControllerModel { throw new Error('Method not implemented.') } connectModel = vi.fn<[scene: Object3D], void>() diff --git a/yarn.lock b/yarn.lock index 3236158..017f5f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -576,6 +576,13 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" @@ -623,6 +630,11 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@^0.3.17": version "0.3.17" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" @@ -747,6 +759,11 @@ resolved "https://registry.yarnpkg.com/@react-three/test-renderer/-/test-renderer-8.2.0.tgz#ac69e4f9abc0f21f341378f3235bfeece4a7f782" integrity sha512-sYTW/9AkU0f03M/rilYaCB9ORD3tS96bUhM+WRsx/QLtOKdUNCWrWwnxutm5M0orON0A0O84gbUosnqvCAKTsw== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@types/chai-subset@^1.3.3": version "1.3.3" resolved "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz" @@ -754,11 +771,16 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@^4.3.4": +"@types/chai@*": version "4.3.4" resolved "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz" integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== +"@types/chai@^4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" + integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng== + "@types/draco3d@^1.4.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/draco3d/-/draco3d-1.4.2.tgz#7faccb809db2a5e19b9efb97c5f2eb9d64d527ea" @@ -1007,41 +1029,48 @@ magic-string "^0.26.2" react-refresh "^0.14.0" -"@vitest/expect@0.29.1": - version "0.29.1" - resolved "https://registry.npmjs.org/@vitest/expect/-/expect-0.29.1.tgz" - integrity sha512-VFt1u34D+/L4pqjLA8VGPdHbdF8dgjX9Nq573L9KG6/7MIAL9jmbEIKpXudmxjoTwcyczOXRyDuUWBQHZafjoA== +"@vitest/expect@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.3.tgz#576e1fd6a3a8b8b7a79a06477f3d450a77d67852" + integrity sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg== dependencies: - "@vitest/spy" "0.29.1" - "@vitest/utils" "0.29.1" + "@vitest/spy" "0.34.3" + "@vitest/utils" "0.34.3" chai "^4.3.7" -"@vitest/runner@0.29.1": - version "0.29.1" - resolved "https://registry.npmjs.org/@vitest/runner/-/runner-0.29.1.tgz" - integrity sha512-VZ6D+kWpd/LVJjvxkt79OA29FUpyrI5L/EEwoBxH5m9KmKgs1QWNgobo/CGQtIWdifLQLvZdzYEK7Qj96w/ixQ== +"@vitest/runner@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.34.3.tgz#ce09b777d133bbcf843e1a67f4a743365764e097" + integrity sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA== dependencies: - "@vitest/utils" "0.29.1" + "@vitest/utils" "0.34.3" p-limit "^4.0.0" - pathe "^1.1.0" + pathe "^1.1.1" + +"@vitest/snapshot@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.34.3.tgz#cb4767aa44711a1072bd2e06204b659275c4f0f2" + integrity sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ== + dependencies: + magic-string "^0.30.1" + pathe "^1.1.1" + pretty-format "^29.5.0" -"@vitest/spy@0.29.1": - version "0.29.1" - resolved "https://registry.npmjs.org/@vitest/spy/-/spy-0.29.1.tgz" - integrity sha512-sRXXK44pPzaizpiZOIQP7YMhxIs80J/b6v1yR3SItpxG952c8tdA7n0O2j4OsVkjiO/ZDrjAYFrXL3gq6hLx6Q== +"@vitest/spy@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.34.3.tgz#d4cf25e6ca9230991a0223ecd4ec2df30f0784ff" + integrity sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ== dependencies: - tinyspy "^1.0.2" + tinyspy "^2.1.1" -"@vitest/utils@0.29.1": - version "0.29.1" - resolved "https://registry.npmjs.org/@vitest/utils/-/utils-0.29.1.tgz" - integrity sha512-6npOEpmyE6zPS2wsWb7yX5oDpp6WY++cC5BX6/qaaMhGC3ZlPd8BbTz3RtGPi1PfPerPvfs4KqS/JDOIaB6J3w== +"@vitest/utils@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.3.tgz#6e243189a358b736b9fc0216e6b6979bc857e897" + integrity sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA== dependencies: - cli-truncate "^3.1.0" - diff "^5.1.0" + diff-sequences "^29.4.3" loupe "^2.3.6" - picocolors "^1.0.0" - pretty-format "^27.5.1" + pretty-format "^29.5.0" "@webgpu/glslang@^0.0.15": version "0.0.15" @@ -1058,12 +1087,17 @@ acorn-walk@^8.2.0: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn@^8.10.0, acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + acorn@^8.7.1: version "8.7.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== -acorn@^8.8.1, acorn@^8.8.2: +acorn@^8.8.2: version "8.8.2" resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== @@ -1088,11 +1122,6 @@ ansi-regex@^5.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" @@ -1112,11 +1141,6 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^6.0.0: - version "6.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - argparse@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" @@ -1286,14 +1310,6 @@ chevrotain@^10.1.2: lodash "4.17.21" regexp-to-ast "0.5.0" -cli-truncate@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz" - integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== - dependencies: - slice-ansi "^5.0.0" - string-width "^5.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -1427,10 +1443,10 @@ detect-gpu@^4.0.19: dependencies: webgl-constants "^1.1.1" -diff@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== +diff-sequences@^29.4.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== dir-glob@^3.0.1: version "3.0.1" @@ -1458,11 +1474,6 @@ draco3d@^1.4.1: resolved "https://registry.npmjs.org/draco3d/-/draco3d-1.5.2.tgz" integrity sha512-AeRQ25Fb29c14vpjnh167UGW0nGY0ZpEM3ld+zEXoEySlmEXcXfsCHZeTgo5qXH925V1JsdjrzasdaQ22/vXog== -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - electron-to-chromium@^1.4.147: version "1.4.161" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.161.tgz" @@ -1473,11 +1484,6 @@ electron-to-chromium@^1.4.284: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.313.tgz" integrity sha512-QckB9OVqr2oybjIrbMI99uF+b9+iTja5weFe0ePbqLb5BHqXOJUO1SG6kDj/1WtWPRIBr51N153AEq8m7HuIaA== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: version "1.20.1" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz" @@ -2219,11 +2225,6 @@ is-extglob@^2.1.1: resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz" - integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -2369,9 +2370,9 @@ lilconfig@^2.0.5: resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz" integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== -local-pkg@^0.4.2: +local-pkg@^0.4.3: version "0.4.3" - resolved "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963" integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g== locate-path@^2.0.0: @@ -2444,6 +2445,13 @@ magic-string@^0.26.2: dependencies: sourcemap-codec "^1.4.8" +magic-string@^0.30.1: + version "0.30.3" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.3.tgz#403755dfd9d6b398dfa40635d52e96c5ac095b85" + integrity sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + media-query-parser@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz" @@ -2491,6 +2499,16 @@ mlly@^1.1.0, mlly@^1.1.1: pkg-types "^1.0.1" ufo "^1.1.0" +mlly@^1.2.0, mlly@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.1.tgz#7ab9cbb040bf8bd8205a0c341ce9acc3ae0c3a74" + integrity sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg== + dependencies: + acorn "^8.10.0" + pathe "^1.1.1" + pkg-types "^1.0.3" + ufo "^1.3.0" + mmd-parser@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz" @@ -2712,6 +2730,11 @@ pathe@^1.1.0: resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz" integrity sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w== +pathe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" + integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== + pathval@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz" @@ -2736,6 +2759,15 @@ pkg-types@^1.0.1: mlly "^1.1.1" pathe "^1.1.0" +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + postcss-load-config@^3.1.0: version "3.1.4" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz" @@ -2777,14 +2809,14 @@ prettier@^2.5.1: resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== -pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== +pretty-format@^29.5.0: + version "29.6.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" + integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== dependencies: - ansi-regex "^5.0.1" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" - react-is "^17.0.1" + react-is "^18.0.0" prop-types@^15.6.0, prop-types@^15.8.1: version "15.8.1" @@ -2820,7 +2852,7 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" -"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.2.0: +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -2830,11 +2862,6 @@ react-is@^16.13.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - react-merge-refs@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz" @@ -3046,24 +3073,11 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz" - integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== - dependencies: - ansi-styles "^6.0.0" - is-fullwidth-code-point "^4.0.0" - source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - sourcemap-codec@^1.4.8: version "1.4.8" resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" @@ -3079,19 +3093,10 @@ stats.js@^0.17.0: resolved "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz" integrity sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw== -std-env@^3.3.1: - version "3.3.2" - resolved "https://registry.npmjs.org/std-env/-/std-env-3.3.2.tgz" - integrity sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA== - -string-width@^5.0.0: - version "5.1.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" +std-env@^3.3.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" + integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== string.prototype.codepointat@^0.2.1: version "0.2.1" @@ -3137,13 +3142,6 @@ strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz" - integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== - dependencies: - ansi-regex "^6.0.1" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" @@ -3154,12 +3152,12 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-literal@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.1.tgz" - integrity sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q== +strip-literal@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.3.0.tgz#db3942c2ec1699e6836ad230090b84bb458e3a07" + integrity sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg== dependencies: - acorn "^8.8.2" + acorn "^8.10.0" supports-color@^5.3.0: version "5.5.0" @@ -3239,20 +3237,20 @@ tiny-inflate@^1.0.3: resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== -tinybench@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz" - integrity sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA== +tinybench@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5" + integrity sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA== -tinypool@^0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz" - integrity sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ== +tinypool@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.7.0.tgz#88053cc99b4a594382af23190c609d93fddf8021" + integrity sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww== -tinyspy@^1.0.2: - version "1.1.1" - resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz" - integrity sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g== +tinyspy@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.1.1.tgz#9e6371b00c259e5c5b301917ca18c01d40ae558c" + integrity sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w== to-fast-properties@^2.0.0: version "2.0.0" @@ -3340,6 +3338,11 @@ ufo@^1.1.0: resolved "https://registry.npmjs.org/ufo/-/ufo-1.1.0.tgz" integrity sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q== +ufo@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.0.tgz#c92f8ac209daff607c57bbd75029e190930a0019" + integrity sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" @@ -3375,15 +3378,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vite-node@0.29.1: - version "0.29.1" - resolved "https://registry.npmjs.org/vite-node/-/vite-node-0.29.1.tgz" - integrity sha512-Ey9bTlQOQrCxQN0oJ7sTg+GrU4nTMLg44iKTFCKf31ry60csqQz4E+Q04hdWhwE4cTgpxUC+zEB1kHbf5jNkFA== +vite-node@0.34.3: + version "0.34.3" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.3.tgz#de134fe38bc1555ac8ab5e489d7df6159a3e1a4c" + integrity sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig== dependencies: cac "^6.7.14" debug "^4.3.4" - mlly "^1.1.0" - pathe "^1.1.0" + mlly "^1.4.0" + pathe "^1.1.1" picocolors "^1.0.0" vite "^3.0.0 || ^4.0.0" @@ -3411,34 +3414,34 @@ vite@^3.0.5: optionalDependencies: fsevents "~2.3.2" -vitest@^0.29.1: - version "0.29.1" - resolved "https://registry.npmjs.org/vitest/-/vitest-0.29.1.tgz" - integrity sha512-iSy6d9VwsIn7pz5I8SjVwdTLDRGKNZCRJVzROwjt0O0cffoymKwazIZ2epyMpRGpeL5tsXAl1cjXiT7agTyVug== +vitest@^0.34.3: + version "0.34.3" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.34.3.tgz#863d61c133d01b16e49fd52d380c09fa5ac03188" + integrity sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ== dependencies: - "@types/chai" "^4.3.4" + "@types/chai" "^4.3.5" "@types/chai-subset" "^1.3.3" "@types/node" "*" - "@vitest/expect" "0.29.1" - "@vitest/runner" "0.29.1" - "@vitest/spy" "0.29.1" - "@vitest/utils" "0.29.1" - acorn "^8.8.1" + "@vitest/expect" "0.34.3" + "@vitest/runner" "0.34.3" + "@vitest/snapshot" "0.34.3" + "@vitest/spy" "0.34.3" + "@vitest/utils" "0.34.3" + acorn "^8.9.0" acorn-walk "^8.2.0" cac "^6.7.14" chai "^4.3.7" debug "^4.3.4" - local-pkg "^0.4.2" - pathe "^1.1.0" + local-pkg "^0.4.3" + magic-string "^0.30.1" + pathe "^1.1.1" picocolors "^1.0.0" - source-map "^0.6.1" - std-env "^3.3.1" - strip-literal "^1.0.0" - tinybench "^2.3.1" - tinypool "^0.3.1" - tinyspy "^1.0.2" + std-env "^3.3.3" + strip-literal "^1.0.1" + tinybench "^2.5.0" + tinypool "^0.7.0" vite "^3.0.0 || ^4.0.0" - vite-node "0.29.1" + vite-node "0.34.3" why-is-node-running "^2.2.2" webgl-constants@^1.1.1: