diff --git a/.yarn/cache/@dnd-kit-accessibility-npm-3.0.1-78bf91a820-0afc2c0fce.zip b/.yarn/cache/@dnd-kit-accessibility-npm-3.0.1-78bf91a820-0afc2c0fce.zip new file mode 100644 index 0000000..363191a Binary files /dev/null and b/.yarn/cache/@dnd-kit-accessibility-npm-3.0.1-78bf91a820-0afc2c0fce.zip differ diff --git a/.yarn/cache/@dnd-kit-core-npm-6.0.8-66053fe203-abe48ff739.zip b/.yarn/cache/@dnd-kit-core-npm-6.0.8-66053fe203-abe48ff739.zip new file mode 100644 index 0000000..00f614e Binary files /dev/null and b/.yarn/cache/@dnd-kit-core-npm-6.0.8-66053fe203-abe48ff739.zip differ diff --git a/.yarn/cache/@dnd-kit-sortable-npm-7.0.2-8871eace61-4ce705aceb.zip b/.yarn/cache/@dnd-kit-sortable-npm-7.0.2-8871eace61-4ce705aceb.zip new file mode 100644 index 0000000..5ba559a Binary files /dev/null and b/.yarn/cache/@dnd-kit-sortable-npm-7.0.2-8871eace61-4ce705aceb.zip differ diff --git a/.yarn/cache/@dnd-kit-utilities-npm-3.2.1-e4b7ea044f-038fd5cc13.zip b/.yarn/cache/@dnd-kit-utilities-npm-3.2.1-e4b7ea044f-038fd5cc13.zip new file mode 100644 index 0000000..b862f3e Binary files /dev/null and b/.yarn/cache/@dnd-kit-utilities-npm-3.2.1-e4b7ea044f-038fd5cc13.zip differ diff --git a/.yarn/cache/@radix-ui-popper-npm-0.1.0-d7a438eda7-f362a9fb8e.zip b/.yarn/cache/@radix-ui-popper-npm-0.1.0-d7a438eda7-f362a9fb8e.zip deleted file mode 100644 index 5fd286e..0000000 Binary files a/.yarn/cache/@radix-ui-popper-npm-0.1.0-d7a438eda7-f362a9fb8e.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-primitive-npm-0.1.0-0a49584f8e-5f721bcfeb.zip b/.yarn/cache/@radix-ui-primitive-npm-0.1.0-0a49584f8e-5f721bcfeb.zip deleted file mode 100644 index 2a95810..0000000 Binary files a/.yarn/cache/@radix-ui-primitive-npm-0.1.0-0a49584f8e-5f721bcfeb.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-arrow-npm-0.1.3-081c30972a-d8dcb85845.zip b/.yarn/cache/@radix-ui-react-arrow-npm-0.1.3-081c30972a-d8dcb85845.zip deleted file mode 100644 index 35e326f..0000000 Binary files a/.yarn/cache/@radix-ui-react-arrow-npm-0.1.3-081c30972a-d8dcb85845.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-compose-refs-npm-0.1.0-a16c93a4d0-d1455577b2.zip b/.yarn/cache/@radix-ui-react-compose-refs-npm-0.1.0-a16c93a4d0-d1455577b2.zip deleted file mode 100644 index 08f7246..0000000 Binary files a/.yarn/cache/@radix-ui-react-compose-refs-npm-0.1.0-a16c93a4d0-d1455577b2.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-context-npm-0.1.1-f6f528ee12-85ed35b6e3.zip b/.yarn/cache/@radix-ui-react-context-npm-0.1.1-f6f528ee12-85ed35b6e3.zip deleted file mode 100644 index 4a38b10..0000000 Binary files a/.yarn/cache/@radix-ui-react-context-npm-0.1.1-f6f528ee12-85ed35b6e3.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-id-npm-0.1.4-b4fa5bb4b3-09ed338c10.zip b/.yarn/cache/@radix-ui-react-id-npm-0.1.4-b4fa5bb4b3-09ed338c10.zip deleted file mode 100644 index 6e20c37..0000000 Binary files a/.yarn/cache/@radix-ui-react-id-npm-0.1.4-b4fa5bb4b3-09ed338c10.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-popper-npm-0.1.3-f38ea29c70-f057a1c583.zip b/.yarn/cache/@radix-ui-react-popper-npm-0.1.3-f38ea29c70-f057a1c583.zip deleted file mode 100644 index 380e82d..0000000 Binary files a/.yarn/cache/@radix-ui-react-popper-npm-0.1.3-f38ea29c70-f057a1c583.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-portal-npm-0.1.3-4c76506063-56836fdff1.zip b/.yarn/cache/@radix-ui-react-portal-npm-0.1.3-4c76506063-56836fdff1.zip deleted file mode 100644 index cc6dff3..0000000 Binary files a/.yarn/cache/@radix-ui-react-portal-npm-0.1.3-4c76506063-56836fdff1.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-portal-npm-0.1.4-5d15e94604-fb047d56c4.zip b/.yarn/cache/@radix-ui-react-portal-npm-0.1.4-5d15e94604-fb047d56c4.zip deleted file mode 100644 index b51c277..0000000 Binary files a/.yarn/cache/@radix-ui-react-portal-npm-0.1.4-5d15e94604-fb047d56c4.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-presence-npm-0.1.1-93a3a72005-b8911eb908.zip b/.yarn/cache/@radix-ui-react-presence-npm-0.1.1-93a3a72005-b8911eb908.zip deleted file mode 100644 index 59afb23..0000000 Binary files a/.yarn/cache/@radix-ui-react-presence-npm-0.1.1-93a3a72005-b8911eb908.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-primitive-npm-0.1.3-f5e2e8eea1-2cce826e9e.zip b/.yarn/cache/@radix-ui-react-primitive-npm-0.1.3-f5e2e8eea1-2cce826e9e.zip deleted file mode 100644 index ac19f9d..0000000 Binary files a/.yarn/cache/@radix-ui-react-primitive-npm-0.1.3-f5e2e8eea1-2cce826e9e.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-primitive-npm-0.1.4-115fd5c8b4-e7b83dc515.zip b/.yarn/cache/@radix-ui-react-primitive-npm-0.1.4-115fd5c8b4-e7b83dc515.zip deleted file mode 100644 index 463cb41..0000000 Binary files a/.yarn/cache/@radix-ui-react-primitive-npm-0.1.4-115fd5c8b4-e7b83dc515.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-slot-npm-0.1.2-b662327955-216927b9b1.zip b/.yarn/cache/@radix-ui-react-slot-npm-0.1.2-b662327955-216927b9b1.zip deleted file mode 100644 index 5c80f39..0000000 Binary files a/.yarn/cache/@radix-ui-react-slot-npm-0.1.2-b662327955-216927b9b1.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-tooltip-npm-0.1.6-6bfafc0cd9-fa409aec05.zip b/.yarn/cache/@radix-ui-react-tooltip-npm-0.1.6-6bfafc0cd9-fa409aec05.zip deleted file mode 100644 index eefedfd..0000000 Binary files a/.yarn/cache/@radix-ui-react-tooltip-npm-0.1.6-6bfafc0cd9-fa409aec05.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-callback-ref-npm-0.1.0-838ec38d13-5356971123.zip b/.yarn/cache/@radix-ui-react-use-callback-ref-npm-0.1.0-838ec38d13-5356971123.zip deleted file mode 100644 index 832aee8..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-callback-ref-npm-0.1.0-838ec38d13-5356971123.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-controllable-state-npm-0.1.0-75d1e06cac-2ddd05854a.zip b/.yarn/cache/@radix-ui-react-use-controllable-state-npm-0.1.0-75d1e06cac-2ddd05854a.zip deleted file mode 100644 index 31be25e..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-controllable-state-npm-0.1.0-75d1e06cac-2ddd05854a.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-escape-keydown-npm-0.1.0-af4910b082-b92769ecf4.zip b/.yarn/cache/@radix-ui-react-use-escape-keydown-npm-0.1.0-af4910b082-b92769ecf4.zip deleted file mode 100644 index 6ab32bf..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-escape-keydown-npm-0.1.0-af4910b082-b92769ecf4.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-layout-effect-npm-0.1.0-d80d7efedb-d8be1f9770.zip b/.yarn/cache/@radix-ui-react-use-layout-effect-npm-0.1.0-d80d7efedb-d8be1f9770.zip deleted file mode 100644 index 2f2b7b6..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-layout-effect-npm-0.1.0-d80d7efedb-d8be1f9770.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-previous-npm-0.1.0-4fbb87052c-3189d18c5c.zip b/.yarn/cache/@radix-ui-react-use-previous-npm-0.1.0-4fbb87052c-3189d18c5c.zip deleted file mode 100644 index a7e0b45..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-previous-npm-0.1.0-4fbb87052c-3189d18c5c.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-rect-npm-0.1.1-4803859eb8-aacf810744.zip b/.yarn/cache/@radix-ui-react-use-rect-npm-0.1.1-4803859eb8-aacf810744.zip deleted file mode 100644 index 6334fd0..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-rect-npm-0.1.1-4803859eb8-aacf810744.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-use-size-npm-0.1.0-bf02f18986-29134b0caf.zip b/.yarn/cache/@radix-ui-react-use-size-npm-0.1.0-bf02f18986-29134b0caf.zip deleted file mode 100644 index aa11af4..0000000 Binary files a/.yarn/cache/@radix-ui-react-use-size-npm-0.1.0-bf02f18986-29134b0caf.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-react-visually-hidden-npm-0.1.3-48d22d0c00-4434bc9524.zip b/.yarn/cache/@radix-ui-react-visually-hidden-npm-0.1.3-48d22d0c00-4434bc9524.zip deleted file mode 100644 index 81d6650..0000000 Binary files a/.yarn/cache/@radix-ui-react-visually-hidden-npm-0.1.3-48d22d0c00-4434bc9524.zip and /dev/null differ diff --git a/.yarn/cache/@radix-ui-rect-npm-0.1.1-1e3be8d132-6f781fe3f6.zip b/.yarn/cache/@radix-ui-rect-npm-0.1.1-1e3be8d132-6f781fe3f6.zip deleted file mode 100644 index d5dec09..0000000 Binary files a/.yarn/cache/@radix-ui-rect-npm-0.1.1-1e3be8d132-6f781fe3f6.zip and /dev/null differ diff --git a/.yarn/cache/@tweakpane-core-npm-2.0.0-dc03f230e8-c92cb9d1d1.zip b/.yarn/cache/@tweakpane-core-npm-2.0.0-dc03f230e8-c92cb9d1d1.zip new file mode 100644 index 0000000..477177a Binary files /dev/null and b/.yarn/cache/@tweakpane-core-npm-2.0.0-dc03f230e8-c92cb9d1d1.zip differ diff --git a/.yarn/cache/@use-gesture-core-npm-10.2.19-d139237291-2ae4d0f3e6.zip b/.yarn/cache/@use-gesture-core-npm-10.2.19-d139237291-2ae4d0f3e6.zip deleted file mode 100644 index ec59c3b..0000000 Binary files a/.yarn/cache/@use-gesture-core-npm-10.2.19-d139237291-2ae4d0f3e6.zip and /dev/null differ diff --git a/.yarn/cache/@use-gesture-react-npm-10.2.19-fc5a25abfc-25835f8843.zip b/.yarn/cache/@use-gesture-react-npm-10.2.19-fc5a25abfc-25835f8843.zip deleted file mode 100644 index 5414550..0000000 Binary files a/.yarn/cache/@use-gesture-react-npm-10.2.19-fc5a25abfc-25835f8843.zip and /dev/null differ diff --git a/.yarn/cache/@welldone-software-why-did-you-render-npm-6.2.3-3f37eda05f-6c012f0a67.zip b/.yarn/cache/@welldone-software-why-did-you-render-npm-6.2.3-3f37eda05f-6c012f0a67.zip deleted file mode 100644 index 9a24034..0000000 Binary files a/.yarn/cache/@welldone-software-why-did-you-render-npm-6.2.3-3f37eda05f-6c012f0a67.zip and /dev/null differ diff --git a/.yarn/cache/assign-symbols-npm-1.0.0-fd803ccdf1-c0eb895911.zip b/.yarn/cache/assign-symbols-npm-1.0.0-fd803ccdf1-c0eb895911.zip deleted file mode 100644 index 6e72b81..0000000 Binary files a/.yarn/cache/assign-symbols-npm-1.0.0-fd803ccdf1-c0eb895911.zip and /dev/null differ diff --git a/.yarn/cache/attr-accept-npm-2.2.2-b9cd0d8eac-496f724935.zip b/.yarn/cache/attr-accept-npm-2.2.2-b9cd0d8eac-496f724935.zip deleted file mode 100644 index 5aabbe2..0000000 Binary files a/.yarn/cache/attr-accept-npm-2.2.2-b9cd0d8eac-496f724935.zip and /dev/null differ diff --git a/.yarn/cache/colord-npm-2.9.3-5c35c27898-95d909bfbc.zip b/.yarn/cache/colord-npm-2.9.3-5c35c27898-95d909bfbc.zip deleted file mode 100644 index 9a082ce..0000000 Binary files a/.yarn/cache/colord-npm-2.9.3-5c35c27898-95d909bfbc.zip and /dev/null differ diff --git a/.yarn/cache/core-util-is-npm-1.0.3-ca74b76c90-9de8597363.zip b/.yarn/cache/core-util-is-npm-1.0.3-ca74b76c90-9de8597363.zip deleted file mode 100644 index 2c844fe..0000000 Binary files a/.yarn/cache/core-util-is-npm-1.0.3-ca74b76c90-9de8597363.zip and /dev/null differ diff --git a/.yarn/cache/dequal-npm-2.0.3-53a630c60e-8679b850e1.zip b/.yarn/cache/dequal-npm-2.0.3-53a630c60e-8679b850e1.zip deleted file mode 100644 index 7721391..0000000 Binary files a/.yarn/cache/dequal-npm-2.0.3-53a630c60e-8679b850e1.zip and /dev/null differ diff --git a/.yarn/cache/extend-shallow-npm-2.0.1-e6ef52b29c-8fb58d9d7a.zip b/.yarn/cache/extend-shallow-npm-2.0.1-e6ef52b29c-8fb58d9d7a.zip deleted file mode 100644 index ba82137..0000000 Binary files a/.yarn/cache/extend-shallow-npm-2.0.1-e6ef52b29c-8fb58d9d7a.zip and /dev/null differ diff --git a/.yarn/cache/extend-shallow-npm-3.0.2-77bbe1bbf5-a920b0cd58.zip b/.yarn/cache/extend-shallow-npm-3.0.2-77bbe1bbf5-a920b0cd58.zip deleted file mode 100644 index ad15ea9..0000000 Binary files a/.yarn/cache/extend-shallow-npm-3.0.2-77bbe1bbf5-a920b0cd58.zip and /dev/null differ diff --git a/.yarn/cache/file-selector-npm-0.5.0-c731a8ed22-f95a069381.zip b/.yarn/cache/file-selector-npm-0.5.0-c731a8ed22-f95a069381.zip deleted file mode 100644 index f9392ea..0000000 Binary files a/.yarn/cache/file-selector-npm-0.5.0-c731a8ed22-f95a069381.zip and /dev/null differ diff --git a/.yarn/cache/for-in-npm-1.0.2-37e3d7aae5-09f4ae93ce.zip b/.yarn/cache/for-in-npm-1.0.2-37e3d7aae5-09f4ae93ce.zip deleted file mode 100644 index 51aeea2..0000000 Binary files a/.yarn/cache/for-in-npm-1.0.2-37e3d7aae5-09f4ae93ce.zip and /dev/null differ diff --git a/.yarn/cache/get-value-npm-2.0.6-03cd422e0a-5c3b99cb53.zip b/.yarn/cache/get-value-npm-2.0.6-03cd422e0a-5c3b99cb53.zip deleted file mode 100644 index 101e5bb..0000000 Binary files a/.yarn/cache/get-value-npm-2.0.6-03cd422e0a-5c3b99cb53.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-assignments-npm-2.0.2-d9ee3ee3f3-efd6051cfd.zip b/.yarn/cache/glsl-token-assignments-npm-2.0.2-d9ee3ee3f3-efd6051cfd.zip deleted file mode 100644 index 1fe3c07..0000000 Binary files a/.yarn/cache/glsl-token-assignments-npm-2.0.2-d9ee3ee3f3-efd6051cfd.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-depth-npm-1.1.2-2c8fa14257-97fff701ee.zip b/.yarn/cache/glsl-token-depth-npm-1.1.2-2c8fa14257-97fff701ee.zip deleted file mode 100644 index 9fa5b97..0000000 Binary files a/.yarn/cache/glsl-token-depth-npm-1.1.2-2c8fa14257-97fff701ee.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-descope-npm-1.0.2-658390c61c-a0d578d5e7.zip b/.yarn/cache/glsl-token-descope-npm-1.0.2-658390c61c-a0d578d5e7.zip deleted file mode 100644 index cd10b0e..0000000 Binary files a/.yarn/cache/glsl-token-descope-npm-1.0.2-658390c61c-a0d578d5e7.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-functions-npm-1.0.1-970f7fe287-65801ee698.zip b/.yarn/cache/glsl-token-functions-npm-1.0.1-970f7fe287-65801ee698.zip deleted file mode 100644 index 190c203..0000000 Binary files a/.yarn/cache/glsl-token-functions-npm-1.0.1-970f7fe287-65801ee698.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-properties-npm-1.0.1-c5dfeda50c-9b4d1caf02.zip b/.yarn/cache/glsl-token-properties-npm-1.0.1-c5dfeda50c-9b4d1caf02.zip deleted file mode 100644 index c9b7efe..0000000 Binary files a/.yarn/cache/glsl-token-properties-npm-1.0.1-c5dfeda50c-9b4d1caf02.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-scope-npm-1.1.2-e6c5871b08-d62812c81a.zip b/.yarn/cache/glsl-token-scope-npm-1.1.2-e6c5871b08-d62812c81a.zip deleted file mode 100644 index ffea413..0000000 Binary files a/.yarn/cache/glsl-token-scope-npm-1.1.2-e6c5871b08-d62812c81a.zip and /dev/null differ diff --git a/.yarn/cache/glsl-token-string-npm-1.0.1-b01fad3c92-3260c1486b.zip b/.yarn/cache/glsl-token-string-npm-1.0.1-b01fad3c92-3260c1486b.zip deleted file mode 100644 index 31d39ee..0000000 Binary files a/.yarn/cache/glsl-token-string-npm-1.0.1-b01fad3c92-3260c1486b.zip and /dev/null differ diff --git a/.yarn/cache/glsl-tokenizer-npm-2.1.5-41724a73eb-daf70e91c6.zip b/.yarn/cache/glsl-tokenizer-npm-2.1.5-41724a73eb-daf70e91c6.zip deleted file mode 100644 index 20d90bb..0000000 Binary files a/.yarn/cache/glsl-tokenizer-npm-2.1.5-41724a73eb-daf70e91c6.zip and /dev/null differ diff --git a/.yarn/cache/immer-npm-9.0.15-6c734225db-92e3d63e81.zip b/.yarn/cache/immer-npm-9.0.15-6c734225db-92e3d63e81.zip deleted file mode 100644 index ba6ea19..0000000 Binary files a/.yarn/cache/immer-npm-9.0.15-6c734225db-92e3d63e81.zip and /dev/null differ diff --git a/.yarn/cache/is-extendable-npm-0.1.1-322b4649ec-3875571d20.zip b/.yarn/cache/is-extendable-npm-0.1.1-322b4649ec-3875571d20.zip deleted file mode 100644 index e3eead3..0000000 Binary files a/.yarn/cache/is-extendable-npm-0.1.1-322b4649ec-3875571d20.zip and /dev/null differ diff --git a/.yarn/cache/is-extendable-npm-1.0.1-7095ad8b16-db07bc1e9d.zip b/.yarn/cache/is-extendable-npm-1.0.1-7095ad8b16-db07bc1e9d.zip deleted file mode 100644 index a2db00a..0000000 Binary files a/.yarn/cache/is-extendable-npm-1.0.1-7095ad8b16-db07bc1e9d.zip and /dev/null differ diff --git a/.yarn/cache/is-plain-object-npm-2.0.4-da3265d804-2a401140cf.zip b/.yarn/cache/is-plain-object-npm-2.0.4-da3265d804-2a401140cf.zip deleted file mode 100644 index 8b68965..0000000 Binary files a/.yarn/cache/is-plain-object-npm-2.0.4-da3265d804-2a401140cf.zip and /dev/null differ diff --git a/.yarn/cache/isarray-npm-0.0.1-92e37e0a70-49191f1425.zip b/.yarn/cache/isarray-npm-0.0.1-92e37e0a70-49191f1425.zip deleted file mode 100644 index 4c3f427..0000000 Binary files a/.yarn/cache/isarray-npm-0.0.1-92e37e0a70-49191f1425.zip and /dev/null differ diff --git a/.yarn/cache/isobject-npm-3.0.1-8145901fd2-db85c4c970.zip b/.yarn/cache/isobject-npm-3.0.1-8145901fd2-db85c4c970.zip deleted file mode 100644 index 214104c..0000000 Binary files a/.yarn/cache/isobject-npm-3.0.1-8145901fd2-db85c4c970.zip and /dev/null differ diff --git a/.yarn/cache/jotai-npm-2.3.0-9e47e9bf85-21df579532.zip b/.yarn/cache/jotai-npm-2.3.0-9e47e9bf85-21df579532.zip new file mode 100644 index 0000000..d1ba796 Binary files /dev/null and b/.yarn/cache/jotai-npm-2.3.0-9e47e9bf85-21df579532.zip differ diff --git a/.yarn/cache/lamina-npm-1.1.23-ee95085731-2b9b0da0ec.zip b/.yarn/cache/lamina-npm-1.1.23-ee95085731-2b9b0da0ec.zip deleted file mode 100644 index 3a839e8..0000000 Binary files a/.yarn/cache/lamina-npm-1.1.23-ee95085731-2b9b0da0ec.zip and /dev/null differ diff --git a/.yarn/cache/leva-npm-0.9.31-165c9663cf-c3caff4249.zip b/.yarn/cache/leva-npm-0.9.31-165c9663cf-c3caff4249.zip deleted file mode 100644 index 22485af..0000000 Binary files a/.yarn/cache/leva-npm-0.9.31-165c9663cf-c3caff4249.zip and /dev/null differ diff --git a/.yarn/cache/leva-npm-0.9.34-d78329d111-72461068ac.zip b/.yarn/cache/leva-npm-0.9.34-d78329d111-72461068ac.zip deleted file mode 100644 index ea732d0..0000000 Binary files a/.yarn/cache/leva-npm-0.9.34-d78329d111-72461068ac.zip and /dev/null differ diff --git a/.yarn/cache/merge-value-npm-1.0.0-30d67b896c-32c0ecaac8.zip b/.yarn/cache/merge-value-npm-1.0.0-30d67b896c-32c0ecaac8.zip deleted file mode 100644 index 20e5fc4..0000000 Binary files a/.yarn/cache/merge-value-npm-1.0.0-30d67b896c-32c0ecaac8.zip and /dev/null differ diff --git a/.yarn/cache/mixin-deep-npm-1.3.2-29b528e571-820d5a51fc.zip b/.yarn/cache/mixin-deep-npm-1.3.2-29b528e571-820d5a51fc.zip deleted file mode 100644 index 543d9a7..0000000 Binary files a/.yarn/cache/mixin-deep-npm-1.3.2-29b528e571-820d5a51fc.zip and /dev/null differ diff --git a/.yarn/cache/react-colorful-npm-5.6.1-ba0c706357-e432b7cb0d.zip b/.yarn/cache/react-colorful-npm-5.6.1-ba0c706357-e432b7cb0d.zip deleted file mode 100644 index 4ee5417..0000000 Binary files a/.yarn/cache/react-colorful-npm-5.6.1-ba0c706357-e432b7cb0d.zip and /dev/null differ diff --git a/.yarn/cache/react-dropzone-npm-12.1.0-56c67f77aa-1be37433cf.zip b/.yarn/cache/react-dropzone-npm-12.1.0-56c67f77aa-1be37433cf.zip deleted file mode 100644 index b96a24b..0000000 Binary files a/.yarn/cache/react-dropzone-npm-12.1.0-56c67f77aa-1be37433cf.zip and /dev/null differ diff --git a/.yarn/cache/readable-stream-npm-1.0.34-db63158f3f-85042c537e.zip b/.yarn/cache/readable-stream-npm-1.0.34-db63158f3f-85042c537e.zip deleted file mode 100644 index eb4518d..0000000 Binary files a/.yarn/cache/readable-stream-npm-1.0.34-db63158f3f-85042c537e.zip and /dev/null differ diff --git a/.yarn/cache/set-value-npm-2.0.1-35da5f8180-09a4bc72c9.zip b/.yarn/cache/set-value-npm-2.0.1-35da5f8180-09a4bc72c9.zip deleted file mode 100644 index 6647983..0000000 Binary files a/.yarn/cache/set-value-npm-2.0.1-35da5f8180-09a4bc72c9.zip and /dev/null differ diff --git a/.yarn/cache/split-string-npm-3.1.0-df5d83450e-ae5af5c91b.zip b/.yarn/cache/split-string-npm-3.1.0-df5d83450e-ae5af5c91b.zip deleted file mode 100644 index 4777e83..0000000 Binary files a/.yarn/cache/split-string-npm-3.1.0-df5d83450e-ae5af5c91b.zip and /dev/null differ diff --git a/.yarn/cache/string_decoder-npm-0.10.31-851f3f7302-fe00f8e303.zip b/.yarn/cache/string_decoder-npm-0.10.31-851f3f7302-fe00f8e303.zip deleted file mode 100644 index 52b4bfd..0000000 Binary files a/.yarn/cache/string_decoder-npm-0.10.31-851f3f7302-fe00f8e303.zip and /dev/null differ diff --git a/.yarn/cache/three-custom-shader-material-npm-4.0.0-f3c6c1ba02-4803fde843.zip b/.yarn/cache/three-custom-shader-material-npm-4.0.0-f3c6c1ba02-4803fde843.zip deleted file mode 100644 index 7eddee2..0000000 Binary files a/.yarn/cache/three-custom-shader-material-npm-4.0.0-f3c6c1ba02-4803fde843.zip and /dev/null differ diff --git a/.yarn/cache/through2-npm-0.6.5-562fbaa3f1-dfea228e31.zip b/.yarn/cache/through2-npm-0.6.5-562fbaa3f1-dfea228e31.zip deleted file mode 100644 index f4efc85..0000000 Binary files a/.yarn/cache/through2-npm-0.6.5-562fbaa3f1-dfea228e31.zip and /dev/null differ diff --git a/.yarn/cache/tunnel-rat-npm-0.1.2-69bf8f367e-9d5975d589.zip b/.yarn/cache/tunnel-rat-npm-0.1.2-69bf8f367e-9d5975d589.zip new file mode 100644 index 0000000..486a1a7 Binary files /dev/null and b/.yarn/cache/tunnel-rat-npm-0.1.2-69bf8f367e-9d5975d589.zip differ diff --git a/.yarn/cache/tweakpane-npm-4.0.0-0250290069-a7f0839556.zip b/.yarn/cache/tweakpane-npm-4.0.0-0250290069-a7f0839556.zip new file mode 100644 index 0000000..27226b2 Binary files /dev/null and b/.yarn/cache/tweakpane-npm-4.0.0-0250290069-a7f0839556.zip differ diff --git a/.yarn/cache/v8n-npm-1.5.1-670231b6b7-96c8dff914.zip b/.yarn/cache/v8n-npm-1.5.1-670231b6b7-96c8dff914.zip deleted file mode 100644 index 9c776a0..0000000 Binary files a/.yarn/cache/v8n-npm-1.5.1-670231b6b7-96c8dff914.zip and /dev/null differ diff --git a/.yarn/cache/zustand-npm-4.1.1-60b4581ecd-03eefb193e.zip b/.yarn/cache/zustand-npm-4.1.1-60b4581ecd-03eefb193e.zip deleted file mode 100644 index abafd9a..0000000 Binary files a/.yarn/cache/zustand-npm-4.1.1-60b4581ecd-03eefb193e.zip and /dev/null differ diff --git a/.yarn/cache/zustand-npm-4.4.0-382083347f-37e69eec1b.zip b/.yarn/cache/zustand-npm-4.4.0-382083347f-37e69eec1b.zip new file mode 100644 index 0000000..3a4b378 Binary files /dev/null and b/.yarn/cache/zustand-npm-4.4.0-382083347f-37e69eec1b.zip differ diff --git a/package.json b/package.json index ae2ab69..ec6e740 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ }, "dependencies": { "@derschmale/io-rgbe": "^0.1.1", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/sortable": "^7.0.2", "@heroicons/react": "^2.0.18", "@radix-ui/react-context-menu": "^1.0.0", "@radix-ui/react-toolbar": "^1.0.2", @@ -22,9 +24,7 @@ "@react-three/postprocessing": "^2.14.9", "clsx": "^1.2.1", "cmdk": "^0.2.0", - "immer": "^9.0.15", - "lamina": "^1.1.23", - "leva": "^0.9.34", + "jotai": "^2.3.0", "prettier": "^2.8.4", "prism-react-renderer": "^1.3.5", "r3f-perf": "^7.1.2", @@ -34,9 +34,11 @@ "sonner": "^0.4.0", "three": "^0.153.0", "three-stdlib": "^2.23.9", - "zustand": "^4.1.1" + "tunnel-rat": "^0.1.2", + "tweakpane": "^4.0.0" }, "devDependencies": { + "@tweakpane/core": "^2.0.0", "@types/prettier": "^2.7.2", "@types/react": "^18.0.19", "@types/react-dom": "^18.0.6", diff --git a/public/textures/checkerboard.png b/public/textures/checkerboard.png deleted file mode 100644 index bc2945c..0000000 Binary files a/public/textures/checkerboard.png and /dev/null differ diff --git a/public/textures/flash-head.exr b/public/textures/flash-head.exr new file mode 100644 index 0000000..5634dd3 Binary files /dev/null and b/public/textures/flash-head.exr differ diff --git a/public/textures/flash-head.png b/public/textures/flash-head.png new file mode 100644 index 0000000..306e849 Binary files /dev/null and b/public/textures/flash-head.png differ diff --git a/public/textures/scrim.png b/public/textures/scrim.png new file mode 100644 index 0000000..3203916 Binary files /dev/null and b/public/textures/scrim.png differ diff --git a/public/textures/softbox-octagon.exr b/public/textures/softbox-octagon.exr new file mode 100644 index 0000000..fba3bc7 Binary files /dev/null and b/public/textures/softbox-octagon.exr differ diff --git a/public/textures/softbox-octagon.png b/public/textures/softbox-octagon.png new file mode 100644 index 0000000..59c742d Binary files /dev/null and b/public/textures/softbox-octagon.png differ diff --git a/public/textures/umbrella.exr b/public/textures/umbrella.exr new file mode 100644 index 0000000..352c035 Binary files /dev/null and b/public/textures/umbrella.exr differ diff --git a/public/textures/umbrella.png b/public/textures/umbrella.png new file mode 100644 index 0000000..1286820 Binary files /dev/null and b/public/textures/umbrella.png differ diff --git a/src/App.tsx b/src/App.tsx index 2007237..2414ed2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,104 +1,18 @@ -import { Bars2Icon } from "@heroicons/react/24/outline"; -import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { Toaster } from "sonner"; - +import { AppContent } from "./components/AppContent"; +import { AppLayout } from "./components/AppLayout"; import { AppToolbar } from "./components/AppToolbar"; import { CommandPalette } from "./components/CommandPalette"; -import { Outliner } from "./components/Outliner/Outliner"; -import { Properties } from "./components/Properties"; -import { HDRIPreview } from "./components/HDRIPreview"; -import { ScenePreview } from "./components/ScenePreview"; -import { useStore } from "./hooks/useStore"; -import { Code } from "./components/Code"; export default function App() { - const mode = useStore((state) => state.mode); return ( -
- - + <> + - - - - - {/* Left */} - - - - - - - - - {/* Middle */} - - - - - - - {mode.hdri && ( - <> - - - - - - - - - )} - - {mode.code && ( - <> - - - - - - - - - )} - - - - - - - - {/* Right */} - - - - -
+ + + + + ); } diff --git a/src/components/AppContent/index.tsx b/src/components/AppContent/index.tsx new file mode 100644 index 0000000..077492d --- /dev/null +++ b/src/components/AppContent/index.tsx @@ -0,0 +1,95 @@ +import { Bars2Icon } from "@heroicons/react/24/outline"; +import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; + +import { Outliner } from "../Outliner/Outliner"; +import { Properties } from "../Properties"; +import { HDRIPreview } from "../HDRIPreview"; +import { ScenePreview } from "../ScenePreview"; +import { Code } from "../Code"; +import { useAtomValue } from "jotai"; +import { modeAtom } from "../../store"; + +export function AppContent() { + const mode = useAtomValue(modeAtom); + + return ( + + {/* Left */} + + + + + + + + + {/* Middle */} + + + + + + + {mode.hdri && ( + <> + + + + + + + + + )} + + {mode.code && ( + <> + + + + + + + + + )} + + + + + + + + {/* Right */} + + + + + ); +} diff --git a/src/components/AppLayout/index.tsx b/src/components/AppLayout/index.tsx new file mode 100644 index 0000000..e4563c7 --- /dev/null +++ b/src/components/AppLayout/index.tsx @@ -0,0 +1,7 @@ +export function AppLayout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/src/components/AppToolbar/index.tsx b/src/components/AppToolbar/index.tsx index 70f2ba8..c5b7589 100644 --- a/src/components/AppToolbar/index.tsx +++ b/src/components/AppToolbar/index.tsx @@ -5,12 +5,13 @@ import { PhotoIcon, } from "@heroicons/react/24/solid"; import * as Toolbar from "@radix-ui/react-toolbar"; -import { useStore } from "../../hooks/useStore"; +import { activeModesAtom, modeAtom } from "../../store"; import { Logo } from "./Logo"; +import { useAtomValue, useSetAtom } from "jotai"; export function AppToolbar() { - const mode = useStore((state) => state.mode); - const setMode = useStore((state) => state.setMode); + const setMode = useSetAtom(modeAtom); + const activeModes = useAtomValue(activeModesAtom); return ( mode[key as keyof typeof mode] - )} + value={activeModes} onValueChange={(modes) => setMode( modes.reduce((acc, mode) => ({ ...acc, [mode]: true }), { diff --git a/src/components/Code/index.tsx b/src/components/Code/index.tsx index 1c20a37..2cc06f7 100644 --- a/src/components/Code/index.tsx +++ b/src/components/Code/index.tsx @@ -1,11 +1,13 @@ -import { useStore } from "../../hooks/useStore"; import { format } from "prettier"; import parserBabel from "prettier/parser-babel"; import theme from "prism-react-renderer/themes/vsDark"; import Highlight, { defaultProps } from "prism-react-renderer"; +import { useAtomValue } from "jotai"; +import { lightsAtom } from "../../store"; +import { latlonToPhiTheta } from "../../utils/coordinates"; export function Code() { - const lights = useStore((state) => state.lights); + const lights = useAtomValue(lightsAtom); const code = ` import React from "react"; @@ -21,6 +23,7 @@ export function Env() { {/* Lights */} ${lights .map((light) => { + const { phi, theta } = latlonToPhiTheta(light.latlon); return ` {/* ${light.name} */} { const down = (e: KeyboardEvent) => { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); - setOpen((open) => !open); + setOpen(true); } }; @@ -40,7 +42,7 @@ export function CommandPalette() { @@ -75,15 +77,31 @@ export function CommandPalette() { subtitle="Deflect light off umbrella" colorTheme="orange" > - + + + + + + + - + @@ -104,7 +122,7 @@ export function CommandPalette() { @@ -120,10 +138,12 @@ export function CommandPalette() {
)} {value === "softbox" && } + {value === "procedural_scrim" && } {value === "umbrella" && } - {value === "flood" && } + {value === "procedural_umbrella" && } + {value === "flash_head" && } {value === "sky" && } - {value === "gradient" && } + {value === "sky_gradient" && }
@@ -144,10 +164,82 @@ function Item({ subtitle: string; colorTheme?: "orange" | "blue" | "green" | "red" | "purple"; }) { + const setOpen = useSetAtom(isCommandPaletteOpenAtom); + const [lights, setLights] = useAtom(lightsAtom); + const addLight = (light: Light) => setLights((lights) => [...lights, light]); + + function handleSelect(value: string) { + const commonProps = { + name: `${value} ${String.fromCharCode(lights.length + 65)}`, + id: THREE.MathUtils.generateUUID(), + ts: Date.now(), + shape: "rect" as const, + latlon: { x: 0, y: 0 }, + intensity: 1, + rotation: 0, + scale: 1, + scaleX: 1, + scaleY: 1, + target: { x: 0, y: 0, z: 0 }, + visible: true, + solo: false, + selected: false, + opacity: 1, + animate: false, + }; + + if (value === "softbox") { + addLight({ + ...commonProps, + type: "texture", + color: "#ffffff", + map: "/textures/softbox-octagon.exr", + }); + } else if (value === "procedural_scrim") { + addLight({ + ...commonProps, + type: "procedural_scrim", + color: "#ffffff", + lightDistance: 0.3, + lightPosition: { x: 0, y: 0 }, + }); + } else if (value === "umbrella") { + addLight({ + ...commonProps, + type: "texture", + color: "#ffffff", + map: "/textures/umbrella.exr", + }); + } else if (value === "flash_head") { + addLight({ + ...commonProps, + type: "texture", + color: "#ffffff", + map: "/textures/flash-head.exr", + }); + } else if (value === "procedural_umbrella") { + addLight({ + ...commonProps, + type: "procedural_umbrella", + color: "#ffffff", + lightSides: 3, + }); + } else if (value === "sky_gradient") { + addLight({ + ...commonProps, + type: "sky_gradient", + color: "#ff0000", + color2: "#0000ff", + }); + } + + setOpen(false); + } + return ( {}} + onSelect={handleSelect} className="group cursor-pointer flex items-center rounded-lg text-sm gap-3 text-neutral-100 p-2 mr-2 font-medium transition-all transition-none data-[selected='true']:bg-blue-500 data-[selected='true']:text-white" style={{ contentVisibility: "auto" }} > @@ -176,7 +268,7 @@ function Item({ function Softbox() { return ( Softbox ); } -function Flood() { +function FlashHead() { return ( Flood + ); +} + +function Umbrella() { + return ( + Umbrella diff --git a/src/components/Effects/index.tsx b/src/components/Effects/index.tsx index e29b269..b0e483b 100644 --- a/src/components/Effects/index.tsx +++ b/src/components/Effects/index.tsx @@ -17,17 +17,17 @@ export function Effects() { {enabled ? ( <> - - ) : ( <> )} + + ); } diff --git a/src/components/Env/LightRenderer.tsx b/src/components/Env/LightRenderer.tsx new file mode 100644 index 0000000..f0e6273 --- /dev/null +++ b/src/components/Env/LightRenderer.tsx @@ -0,0 +1,89 @@ +import { Sphere } from "@react-three/drei"; +import { useFrame } from "@react-three/fiber"; +import { PrimitiveAtom, useAtomValue } from "jotai"; +import { useRef } from "react"; +import * as THREE from "three"; +import { + Light, + ProceduralScrimLight, + ProceduralUmbrellaLight, + SkyGradientLight, + TextureLight, +} from "../../store"; +import { ProceduralScrimLightMaterial } from "./ProceduralScrimLightMaterial"; +import { ProceduralUmbrellaLightMaterial } from "./ProceduralUmbrellaLightMaterial"; +import { SkyGradientLightMaterial } from "./SkyGradientLightMaterial"; +import { TextureLightMaterial } from "./TextureLightMaterial"; +import { latlonToPhiTheta } from "../../utils/coordinates"; + +export function LightRenderer({ + index, + lightAtom, +}: { + index: number; + lightAtom: PrimitiveAtom; +}) { + const meshRef = useRef(null); + const light = useAtomValue(lightAtom); + + useFrame(() => { + if (!meshRef.current) { + return; + } + + const { phi, theta } = latlonToPhiTheta(light.latlon); + + meshRef.current.position.setFromSphericalCoords(3, phi, theta); + + meshRef.current.scale.setX(light.scale * light.scaleX); + meshRef.current.scale.setY(light.scale * light.scaleY); + meshRef.current.scale.setZ(light.scale); + + meshRef.current.lookAt(light.target.x, light.target.y, light.target.z); + meshRef.current.rotateZ(light.rotation); + meshRef.current.updateMatrix(); + }); + + if (light.type === "sky_gradient") { + return ( + + } + /> + + ); + } + + return ( + + + {light.type === "procedural_scrim" && ( + } + /> + )} + {light.type === "texture" && ( + } + /> + )} + {light.type === "procedural_umbrella" && ( + } + /> + )} + + ); +} diff --git a/src/components/Env/LightformerLayers.tsx b/src/components/Env/LightformerLayers.tsx deleted file mode 100644 index e8f7b02..0000000 --- a/src/components/Env/LightformerLayers.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as THREE from "three"; -import { Gradient, Noise, Color, Texture } from "lamina"; -import { Light } from "../../hooks/useStore"; - -export function LightformerLayers({ light }: { light: Light }) { - if (light.type === "solid") { - const color = new THREE.Color(light.color); - color.multiplyScalar(light.intensity); - return ; - } else if (light.type === "gradient") { - const colorA = new THREE.Color(light.colorA); - const colorB = new THREE.Color(light.colorB); - colorA.multiplyScalar(light.intensity); - colorB.multiplyScalar(light.intensity); - return ( - - ); - } else if (light.type === "noise") { - const colorA = new THREE.Color(light.colorA); - const colorB = new THREE.Color(light.colorB); - const colorC = new THREE.Color(light.colorC); - const colorD = new THREE.Color(light.colorD); - colorA.multiplyScalar(light.intensity); - colorB.multiplyScalar(light.intensity); - colorC.multiplyScalar(light.intensity); - colorD.multiplyScalar(light.intensity); - return ( - - ); - } else if (light.type === "texture") { - return ; - } else { - throw new Error("Unknown light type"); - } -} diff --git a/src/components/Env/ProceduralScrimLightMaterial.tsx b/src/components/Env/ProceduralScrimLightMaterial.tsx new file mode 100644 index 0000000..b9d95d2 --- /dev/null +++ b/src/components/Env/ProceduralScrimLightMaterial.tsx @@ -0,0 +1,161 @@ +import { shaderMaterial } from "@react-three/drei"; +import { + MaterialNode, + ThreeElements, + extend, + useFrame, +} from "@react-three/fiber"; +import { useRef, useState } from "react"; +import * as THREE from "three"; +import { Light, ProceduralScrimLight } from "../../store"; +import { PrimitiveAtom, useAtomValue } from "jotai"; + +const vertexShader = /* glsl */ ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +const fragmentShader = /* glsl */ ` + #define PI 3.14159265359 + #define TWO_PI 6.28318530718 + + uniform float uOpacity; + uniform vec3 uColor; + uniform float uIntensity; + uniform vec2 uLightPosition; + uniform float uLightDistance; + + varying vec2 vUv; + + // SOURCE: https://articles.hyperknot.com/area_lights_in_shaders/ + + float point_light(vec2 uv, float h, float i) { + // h - light's height over the ground + // i - light's intensity + return i * h * pow(dot(uv, uv) + h * h, -1.5); + } + + float disc_light(vec2 uv, float h, float i) { + // h - light's height over the ground + // i - light's intensity + if (uv.x > 0.) return 0.; + return i * h * -uv.x * pow(dot(uv,uv) + h*h, -2.); + } + + float rod_light_antideriv(vec2 uv, float i, float h) { + return i * uv.x / (dot(uv,uv) + h*h); + } + + float rod_light(vec2 uv, float i, float h_top, float h_bottom) { + // h_top and h_bottom - the light's top and bottom above the ground + // i - light's intensity + return rod_light_antideriv(uv, i, h_top) - rod_light_antideriv(uv, i, h_bottom); + } + + float area_light_antideriv(vec2 uv, float i, float h, float t) { + float lxh = length(vec2(uv.x, h)); + return -i * uv.x * atan((t-uv.y) / lxh) / lxh; + } + + float area_light(vec2 uv, float i, float h_bottom, float h_top, float t_start, float t_end) { + // i - light's intensity + // h_top and h_bottom - the light's top and bottom above the ground + // t_start and t_end - the light's start and end on the y-axis + float v = + + area_light_antideriv(uv, i, h_top, t_end) + + area_light_antideriv(uv, i, h_bottom, t_start) + - area_light_antideriv(uv, i, h_bottom, t_end) + - area_light_antideriv(uv, i, h_top, t_start); + return max(0.0, v); + } + + void main() { + vec2 uv = vUv; + uv = (2.0 * uv - 1.0); + uv -= uLightPosition; + + float l = 0.0; + l = point_light(uv, uLightDistance, uIntensity); + // l = disc_light(uv, uLightDistance, uIntensity); + // l = rod_light(uv, uIntensity, uLightDistance, uLightDistance + 0.5); + // l = area_light(uv, uIntensity, uLightDistance, uLightDistance + 0.5, -0.25, 0.25); + + // Clamp to 0 + l = max(0.0, l); + + vec3 color = vec3(l); + + // Apply intensity and color + color *= uColor; + + gl_FragColor = vec4(color, uOpacity); + } +`; + +const ProceduralScrimLightShaderMaterial = shaderMaterial( + { + uOpacity: 1, + uColor: new THREE.Color(0xffffff), + uIntensity: 1, + uLightPosition: new THREE.Vector2(0, 0), + uLightDistance: 0.5, + }, + vertexShader, + fragmentShader +); + +extend({ ProceduralScrimLightShaderMaterial }); + +declare module "@react-three/fiber" { + interface ThreeElements { + proceduralScrimLightShaderMaterial: MaterialNode< + any, + typeof ProceduralScrimLightShaderMaterial + >; + } +} + +export function ProceduralScrimLightMaterial({ + lightAtom, +}: { + lightAtom: PrimitiveAtom; +}) { + const ref = useRef( + null! + ); + + const light = useAtomValue(lightAtom); + + const [color] = useState(() => new THREE.Color(0xffffff)); + + useFrame(() => { + ref.current.uniforms.uColor.value = color.set(light.color); + ref.current.uniforms.uIntensity.value = light.intensity; + ref.current.uniforms.uOpacity.value = light.opacity; + ref.current.uniforms.uLightPosition.value = new THREE.Vector2( + light.lightPosition.x, + light.lightPosition.y + ); + ref.current.uniforms.uLightDistance.value = light.lightDistance; + }); + + return ( + + ); +} + +// Reload on HMR +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + window.location.reload(); + }); +} diff --git a/src/components/Env/ProceduralUmbrellaLightMaterial.tsx b/src/components/Env/ProceduralUmbrellaLightMaterial.tsx new file mode 100644 index 0000000..aa89b98 --- /dev/null +++ b/src/components/Env/ProceduralUmbrellaLightMaterial.tsx @@ -0,0 +1,107 @@ +import { shaderMaterial } from "@react-three/drei"; +import { + MaterialNode, + ThreeElements, + extend, + useFrame, +} from "@react-three/fiber"; +import { PrimitiveAtom, useAtomValue } from "jotai"; +import { useRef } from "react"; +import * as THREE from "three"; +import { ProceduralUmbrellaLight } from "../../store"; + +const vertexShader = /* glsl */ ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +const fragmentShader = /* glsl */ ` + #define PI 3.14159265359 + #define TWO_PI 6.28318530718 + + uniform float uOpacity; + uniform vec3 uColor; + uniform float uIntensity; + uniform float uLightSides; + + varying vec2 vUv; + + void main() { + vec2 uv = vUv; + uv = 2.0 * uv - 1.0; + + vec2 p = uv; + float at = atan(p.y, p.x); + float angle = sin(fract(at / PI * uLightSides) * PI) * PI * 0.5; + float radius = length(p); + float intensity = 1.0 - smoothstep(0.8, 1.0, radius * radius); + + vec3 color = vec3(angle * intensity); + + // Apply intensity and color + color *= uColor * uIntensity; + float alpha = smoothstep(0.0, 0.5, intensity) * uOpacity; + + gl_FragColor = vec4(color, alpha); + } +`; + +const ProceduralUmbrellaLightShaderMaterial = shaderMaterial( + { + uOpacity: 1, + uColor: new THREE.Color(0xffffff), + uIntensity: 1, + uLightSides: 4, + }, + vertexShader, + fragmentShader +); + +extend({ ProceduralUmbrellaLightShaderMaterial }); + +declare module "@react-three/fiber" { + interface ThreeElements { + proceduralUmbrellaLightShaderMaterial: MaterialNode< + any, + typeof ProceduralUmbrellaLightShaderMaterial + >; + } +} + +export function ProceduralUmbrellaLightMaterial({ + lightAtom, +}: { + lightAtom: PrimitiveAtom; +}) { + const light = useAtomValue(lightAtom); + const ref = useRef( + null! + ); + + useFrame(() => { + ref.current.uniforms.uOpacity.value = light.opacity; + ref.current.uniforms.uIntensity.value = light.intensity; + ref.current.uniforms.uColor.value = new THREE.Color(light.color); + ref.current.uniforms.uLightSides.value = light.lightSides; + }); + + return ( + + ); +} + +// Reload on HMR +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + window.location.reload(); + }); +} diff --git a/src/components/Env/SkyGradientLightMaterial.tsx b/src/components/Env/SkyGradientLightMaterial.tsx new file mode 100644 index 0000000..7b8cfe1 --- /dev/null +++ b/src/components/Env/SkyGradientLightMaterial.tsx @@ -0,0 +1,93 @@ +import { shaderMaterial } from "@react-three/drei"; +import { + MaterialNode, + ThreeElements, + extend, + useFrame, +} from "@react-three/fiber"; +import { PrimitiveAtom, useAtomValue } from "jotai"; +import { useRef } from "react"; +import * as THREE from "three"; +import { SkyGradientLight } from "../../store"; + +const vertexShader = /* glsl */ ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +const fragmentShader = /* glsl */ ` + #define PI 3.14159265359 + #define TWO_PI 6.28318530718 + + uniform float uOpacity; + uniform vec3 uColor; + uniform vec3 uColor2; + uniform float uIntensity; + + varying vec2 vUv; + + void main() { + vec2 uv = vUv; + vec3 color = mix(uColor, uColor2, uv.y); + gl_FragColor = vec4(color * uIntensity, uOpacity); + } +`; + +const SkyGradientLightShaderMaterial = shaderMaterial( + { + uOpacity: 1, + uColor: new THREE.Color(0xffffff), + uColor2: new THREE.Color(0xffffff), + uIntensity: 1, + }, + vertexShader, + fragmentShader +); + +extend({ SkyGradientLightShaderMaterial }); + +declare module "@react-three/fiber" { + interface ThreeElements { + skyGradientLightShaderMaterial: MaterialNode< + any, + typeof SkyGradientLightShaderMaterial + >; + } +} + +export function SkyGradientLightMaterial({ + lightAtom, +}: { + lightAtom: PrimitiveAtom; +}) { + const light = useAtomValue(lightAtom); + const ref = useRef(null!); + + useFrame(() => { + ref.current.uniforms.uOpacity.value = light.opacity; + ref.current.uniforms.uIntensity.value = light.intensity; + ref.current.uniforms.uColor.value = new THREE.Color(light.color); + ref.current.uniforms.uColor2.value = new THREE.Color(light.color2); + }); + + return ( + + ); +} + +// Reload on HMR +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + window.location.reload(); + }); +} diff --git a/src/components/Env/TextureLightMaterial.tsx b/src/components/Env/TextureLightMaterial.tsx new file mode 100644 index 0000000..7a13014 --- /dev/null +++ b/src/components/Env/TextureLightMaterial.tsx @@ -0,0 +1,97 @@ +import { shaderMaterial } from "@react-three/drei"; +import { + MaterialNode, + ThreeElements, + extend, + useFrame, + useLoader, +} from "@react-three/fiber"; +import { PrimitiveAtom, useAtomValue } from "jotai"; +import { useRef } from "react"; +import * as THREE from "three"; +import { EXRLoader } from "three-stdlib"; +import { TextureLight } from "../../store"; + +const vertexShader = /* glsl */ ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +const fragmentShader = /* glsl */ ` + #define PI 3.14159265359 + #define TWO_PI 6.28318530718 + + uniform float uOpacity; + uniform vec3 uColor; + uniform float uIntensity; + uniform sampler2D uTexture; + + varying vec2 vUv; + + void main() { + vec2 uv = vUv; + + vec4 tex = texture2D(uTexture, uv); + + gl_FragColor = vec4(tex.rgb * uColor * uIntensity, tex.a * uOpacity); + } +`; + +const TextureLightShaderMaterial = shaderMaterial( + { + uOpacity: 1, + uColor: new THREE.Color(0xffffff), + uIntensity: 1, + uTexture: new THREE.DataTexture(), + }, + vertexShader, + fragmentShader +); + +extend({ TextureLightShaderMaterial }); + +declare module "@react-three/fiber" { + interface ThreeElements { + textureLightShaderMaterial: MaterialNode< + any, + typeof TextureLightShaderMaterial + >; + } +} + +export function TextureLightMaterial({ + lightAtom, +}: { + lightAtom: PrimitiveAtom; +}) { + const light = useAtomValue(lightAtom); + const ref = useRef(null!); + const texture = useLoader(EXRLoader, light.map); + + useFrame(() => { + ref.current.uniforms.uOpacity.value = light.opacity; + ref.current.uniforms.uIntensity.value = light.intensity; + ref.current.uniforms.uColor.value = new THREE.Color(light.color); + }); + + return ( + + ); +} + +// Reload on HMR +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + window.location.reload(); + }); +} diff --git a/src/components/Env/index.tsx b/src/components/Env/index.tsx index 6a02fa3..73a5c69 100644 --- a/src/components/Env/index.tsx +++ b/src/components/Env/index.tsx @@ -1,133 +1,20 @@ -import * as THREE from "three"; -import { Environment, Float, Lightformer } from "@react-three/drei"; -import { LayerMaterial } from "lamina"; -import { useControls, folder, LevaInputs } from "leva"; -import { useStore } from "../../hooks/useStore"; -import { LightformerLayers } from "./LightformerLayers"; -import { useState } from "react"; +import { lightAtomsAtom } from "../../store"; +import { useAtomValue } from "jotai"; +import { LightRenderer } from "./LightRenderer"; export function Env() { - const mode = useStore((state) => state.mode); - const lights = useStore((state) => state.lights); - const selectedLightId = useStore((state) => state.selectedLightId); - - const [{ background, backgroundColor, preset, blur }] = useControls(() => { - return { - Background: folder( - { - background: { - label: "Show BG", - value: true, - render: () => selectedLightId === null && mode.scene, - }, - preset: { - type: LevaInputs.SELECT, - label: "Preset", - value: "", - options: [ - "", - "sunset", - "dawn", - "night", - "warehouse", - "forest", - "apartment", - "studio", - "city", - "park", - "lobby", - ] as const, - }, - backgroundColor: { - label: "BG Color", - value: "#000000", - render: () => selectedLightId === null && mode.scene, - }, - blur: { - label: "Blur", - value: 0, - min: 0, - max: 1, - }, - }, - { - order: 0, - color: "cyan", - collapsed: true, - } - ), - }; - }, [selectedLightId]); - - const [resolution, setResolution] = useState(2048); + const lightAtoms = useAtomValue(lightAtomsAtom); return ( - - - {lights.map((light) => { - const { - id, - distance, - phi, - theta, - intensity, - shape, - scale, - scaleX, - scaleY, - visible, - rotation, - opacity, - animate, - animationSpeed, - animationFloatIntensity, - animationRotationIntensity, - animationFloatingRange, - } = light; - - return ( - - - - - - - - ); - })} - + <> + + {lightAtoms.map((lightAtom, i) => ( + + ))} + ); } diff --git a/src/components/HDRIPreview/DownloadHDRI.tsx b/src/components/HDRIPreview/DownloadHDRI.tsx index 93b8570..0aa390f 100644 --- a/src/components/HDRIPreview/DownloadHDRI.tsx +++ b/src/components/HDRIPreview/DownloadHDRI.tsx @@ -1,135 +1,132 @@ import { useThree } from "@react-three/fiber"; import * as THREE from "three"; import convertCubemapToEquirectangular from "./convertCubemapToEquirectangular"; -import { useStore } from "../../hooks/useStore"; -import { button, folder, LevaInputs, useControls } from "leva"; import { encodeRGBE, HDRImageData } from "@derschmale/io-rgbe"; export function DownloadHDRI({ texture }: { texture: THREE.CubeTexture }) { const renderer = useThree((state) => state.gl); - const selectedLightId = useStore((state) => state.selectedLightId); - useControls( - () => ({ - "Export Settings": folder( - { - resolution: { - label: "Resolution", - type: LevaInputs.SELECT, - options: { - "1k": [1024, 512], - "2k": [2048, 1024], - "4k": [4096, 2048], - }, - }, - format: { - label: "Format", - type: LevaInputs.SELECT, - options: { - HDR: "image/vnd.radiance", - PNG: "image/png", - JPEG: "image/jpg", - WEBP: "image/webp", - }, - }, - "Export HDRI": button((get) => { - const [width, height] = get("Export Settings.resolution"); - const format = get("Export Settings.format"); - const filename = - format === "image/vnd.radiance" - ? "envmap.hdr" - : format === "image/png" - ? "envmap.png" - : format === "image/jpg" - ? "envmap.jpg" - : format === "image/webp" - ? "envmap.webp" - : "envmap"; + // useControls( + // () => ({ + // "Export Settings": folder( + // { + // resolution: { + // label: "Resolution", + // type: LevaInputs.SELECT, + // options: { + // "1k": [1024, 512], + // "2k": [2048, 1024], + // "4k": [4096, 2048], + // }, + // }, + // format: { + // label: "Format", + // type: LevaInputs.SELECT, + // options: { + // HDR: "image/vnd.radiance", + // PNG: "image/png", + // JPEG: "image/jpg", + // WEBP: "image/webp", + // }, + // }, + // "Export HDRI": button((get) => { + // const [width, height] = get("Export Settings.resolution"); + // const format = get("Export Settings.format"); + // const filename = + // format === "image/vnd.radiance" + // ? "envmap.hdr" + // : format === "image/png" + // ? "envmap.png" + // : format === "image/jpg" + // ? "envmap.jpg" + // : format === "image/webp" + // ? "envmap.webp" + // : "envmap"; - if (format === "image/vnd.radiance") { - const fbo = convertCubemapToEquirectangular( - texture, - renderer, - width, - height, - THREE.SRGBColorSpace, - THREE.FloatType - ); + // if (format === "image/vnd.radiance") { + // const fbo = convertCubemapToEquirectangular( + // texture, + // renderer, + // width, + // height, + // THREE.SRGBColorSpace, + // THREE.FloatType + // ); - const pixels = new Float32Array(width * height * 4); - renderer.readRenderTargetPixels(fbo, 0, 0, width, height, pixels); + // const pixels = new Float32Array(width * height * 4); + // renderer.readRenderTargetPixels(fbo, 0, 0, width, height, pixels); - // Convert RBGA buffer to RGB - const rgbPixels = new Float32Array(width * height * 3); - for (let i = 0; i < width * height; i++) { - rgbPixels[i * 3] = pixels[i * 4]; - rgbPixels[i * 3 + 1] = pixels[i * 4 + 1]; - rgbPixels[i * 3 + 2] = pixels[i * 4 + 2]; - } + // // Convert RBGA buffer to RGB + // const rgbPixels = new Float32Array(width * height * 3); + // for (let i = 0; i < width * height; i++) { + // rgbPixels[i * 3] = pixels[i * 4]; + // rgbPixels[i * 3 + 1] = pixels[i * 4 + 1]; + // rgbPixels[i * 3 + 2] = pixels[i * 4 + 2]; + // } - const imgData = new HDRImageData(); - imgData.width = width; - imgData.height = height; - imgData.exposure = 1; - imgData.gamma = 1; - imgData.data = rgbPixels; + // const imgData = new HDRImageData(); + // imgData.width = width; + // imgData.height = height; + // imgData.exposure = 1; + // imgData.gamma = 1; + // imgData.data = rgbPixels; - const blob = new Blob([encodeRGBE(imgData)], { - type: "application/octet-stream", - }); - const url = URL.createObjectURL(blob); + // const blob = new Blob([encodeRGBE(imgData)], { + // type: "application/octet-stream", + // }); + // const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.download = filename; - link.href = url; - link.click(); + // const link = document.createElement("a"); + // link.download = filename; + // link.href = url; + // link.click(); - URL.revokeObjectURL(url); - } else { - const fbo = convertCubemapToEquirectangular( - texture, - renderer, - width, - height - ); + // URL.revokeObjectURL(url); + // } else { + // const fbo = convertCubemapToEquirectangular( + // texture, + // renderer, + // width, + // height + // ); - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; + // const canvas = document.createElement("canvas"); + // canvas.width = width; + // canvas.height = height; - const ctx = canvas.getContext("2d"); - if (ctx) { - const pixels = new Uint8Array(width * height * 4); - renderer.readRenderTargetPixels( - fbo, - 0, - 0, - width, - height, - pixels - ); - const imageData = new ImageData( - new Uint8ClampedArray(pixels), - width, - height - ); - ctx.putImageData(imageData, 0, 0); - const link = document.createElement("a"); - link.download = filename; - link.href = canvas.toDataURL(format, 1); - link.click(); - } - } - }), - }, - { - order: 3, - color: "magenta", - } - ), - }), - [selectedLightId, texture] - ); + // const ctx = canvas.getContext("2d"); + // if (ctx) { + // const pixels = new Uint8Array(width * height * 4); + // renderer.readRenderTargetPixels( + // fbo, + // 0, + // 0, + // width, + // height, + // pixels + // ); + // const imageData = new ImageData( + // new Uint8ClampedArray(pixels), + // width, + // height + // ); + // ctx.putImageData(imageData, 0, 0); + // const link = document.createElement("a"); + // link.download = filename; + // link.href = canvas.toDataURL(format, 1); + // link.click(); + // } + // } + // }), + // }, + // { + // order: 3, + // color: "magenta", + // } + // ), + // }), + // [selectedLightId, texture] + // ); return null; } diff --git a/src/components/HDRIPreview/EnvMapPlane.tsx b/src/components/HDRIPreview/EnvMapPlane.tsx index 6b72647..f058282 100644 --- a/src/components/HDRIPreview/EnvMapPlane.tsx +++ b/src/components/HDRIPreview/EnvMapPlane.tsx @@ -5,6 +5,7 @@ import { CubeMaterial } from "./CubeMaterial"; import { Env } from "../Env"; import { DownloadHDRI } from "./DownloadHDRI"; import { SaveBackgroundTexture } from "./SaveBackgroundTexture"; +import { Environment } from "@react-three/drei"; export function EnvMapPlane() { const [texture, setTexture] = useState(() => new THREE.CubeTexture()); @@ -16,7 +17,15 @@ export function EnvMapPlane() { <> - + + + , scene )} diff --git a/src/components/HDRIPreview/convertCubemapToEquirectangular.ts b/src/components/HDRIPreview/convertCubemapToEquirectangular.ts index dc87634..6eb67cb 100644 --- a/src/components/HDRIPreview/convertCubemapToEquirectangular.ts +++ b/src/components/HDRIPreview/convertCubemapToEquirectangular.ts @@ -34,7 +34,7 @@ void main() { normalize( dir ); gl_FragColor = textureCube( map, dir ); - // #include + #include #include } `; diff --git a/src/components/Model/index.tsx b/src/components/Model/index.tsx index 528970d..15f3aad 100644 --- a/src/components/Model/index.tsx +++ b/src/components/Model/index.tsx @@ -1,10 +1,12 @@ import { Resize, useGLTF } from "@react-three/drei"; +import { useAtomValue } from "jotai"; import { useMemo } from "react"; import * as THREE from "three"; -import { useStore } from "../../hooks/useStore"; +import { modelUrlAtom } from "../../store"; export function Model({ debugMaterial, ...props }: any) { - const modelUrl = useStore((state) => state.modelUrl); + const modelUrl = useAtomValue(modelUrlAtom); + const { scene, // @ts-ignore @@ -34,8 +36,10 @@ export function Model({ debugMaterial, ...props }: any) { }, [nodes, materials, debugMaterial]); return ( - - - + + + + + ); } diff --git a/src/components/Outliner/CameraListItem.tsx b/src/components/Outliner/CameraListItem.tsx index 38bc0ca..b64f9b4 100644 --- a/src/components/Outliner/CameraListItem.tsx +++ b/src/components/Outliner/CameraListItem.tsx @@ -1,46 +1,55 @@ import { CameraIcon } from "@heroicons/react/24/outline"; import clsx from "clsx"; -import { useStore, Camera } from "../../hooks/useStore"; +import { + Camera, + selectCameraAtom, + toggleCameraSelectionAtom, +} from "../../store"; import { useKeyPress } from "../../hooks/useKeyPress"; +import { PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; -export function CameraListItem({ index, camera }: { index: number; camera: Camera; }) { - const { id, name } = camera; - const selectedCameraId = useStore((state) => state.selectedCameraId); - const setSelectedCameraId = useStore((state) => state.setSelectedCameraId); +export function CameraListItem({ + index, + cameraAtom, +}: { + index: number; + cameraAtom: PrimitiveAtom; +}) { + const camera = useAtomValue(cameraAtom); - const isSelected = selectedCameraId === id; + const toggleCameraSelection = useSetAtom(toggleCameraSelectionAtom); + const selectCamera = useSetAtom(selectCameraAtom); const key = String(index + 1); - useKeyPress(key, () => setSelectedCameraId(id)); + useKeyPress(key, () => selectCamera(camera.id)); return (
  • { - if (!isSelected) { - setSelectedCameraId(id); - } - }} + onClick={() => toggleCameraSelection(camera.id)} > + checked={camera.selected} + className="peer" + /> - {name} + + {camera.name} + {key} diff --git a/src/components/Outliner/DragHandleIcon.tsx b/src/components/Outliner/DragHandleIcon.tsx new file mode 100644 index 0000000..5f2b0c1 --- /dev/null +++ b/src/components/Outliner/DragHandleIcon.tsx @@ -0,0 +1,12 @@ +export function DragHandleIcon(props: React.SVGProps) { + return ( + + + + + + + + + ); +} diff --git a/src/components/Outliner/LightListItem.tsx b/src/components/Outliner/LightListItem.tsx index 414ca2a..ecb3169 100644 --- a/src/components/Outliner/LightListItem.tsx +++ b/src/components/Outliner/LightListItem.tsx @@ -1,4 +1,6 @@ import { + Bars2Icon, + ChevronUpDownIcon, EyeSlashIcon, FlagIcon, LightBulbIcon, @@ -9,382 +11,158 @@ import { } from "@heroicons/react/24/solid"; import * as ContextMenu from "@radix-ui/react-context-menu"; import clsx from "clsx"; -import { folder, useControls } from "leva"; -import { useStore, Light } from "../../hooks/useStore"; - -export function LightListItem({ light }: { light: Light }) { - const { - id, - type, - name, - visible, - solo, - shape, - intensity, - distance, - phi, - theta, - scale, - scaleX, - scaleY, - opacity, - animate, - } = light; - - const selectedLightId = useStore((state) => state.selectedLightId); - const toggleLightVisibilityById = useStore( - (state) => state.toggleLightVisibilityById - ); - const setSelectedLightId = useStore((state) => state.setSelectedLightId); - const clearSelectedLight = useStore((state) => state.clearSelectedLight); - const updateLight = useStore((state) => state.updateLight); - const duplicateLightById = useStore((state) => state.duplicateLightById); - const removeLightById = useStore((state) => state.removeLightById); - const toggleSoloLightById = useStore((state) => state.toggleSoloLightById); - const isSolo = useStore((state) => state.isSolo); - const textureMaps = useStore((state) => state.textureMaps); +import { + Light, + isSoloAtom, + toggleSoloAtom, + toggleLightSelectionAtom, + deleteLightAtom, + duplicateLightAtom, +} from "../../store"; +import { PropertiesPanelTunnel } from "../Properties"; +import { PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; +import { LightProperties } from "./LightProperties"; +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { DragHandleIcon } from "./DragHandleIcon"; - useControls(() => { - if (selectedLightId !== id) { - return { - Light: folder( - {}, - { - color: "yellow", - order: 2, - } - ), - }; - } else { - return { - Light: folder( - { - [`name ~${id}`]: { - label: "Name", - value: name ?? "Light", - onChange: (v) => updateLight({ id, name: v }), - }, - [`shape ~${id}`]: { - label: "Shape", - value: shape ?? "rect", - options: ["rect", "ring", "circle"], - onChange: (v) => updateLight({ id, shape: v }), - }, - [`intensity ~${id}`]: { - label: "Intensity", - value: intensity, - step: 0.1, - min: 0, - onChange: (v) => updateLight({ id, intensity: v }), - }, - [`opacity ~${id}`]: { - label: "Opacity", - value: opacity ?? 1, - step: 0.1, - min: 0, - max: 1, - onChange: (v) => updateLight({ id, opacity: v }), - }, - [`scaleMultiplier ~${id}`]: { - label: "Scale Multiplier", - value: scale ?? 1, - step: 0.1, - min: 0, - max: 10, - onChange: (v) => updateLight({ id, scale: v }), - }, - [`scale ~${id}`]: { - label: "Scale", - value: [scaleX, scaleY] ?? [1, 1], - step: 0.1, - min: 0, - joystick: false, - onChange: (v) => updateLight({ id, scaleX: v[0], scaleY: v[1] }), - }, - [`distance ~${id}`]: { - label: "Distance", - value: distance ?? 1, - step: 0.1, - min: 0, - onChange: (v) => updateLight({ id, distance: v }), - }, - [`phi ~${id}`]: { - label: "Phi", - value: phi ?? 1, - step: 0.1, - min: 0, - max: Math.PI, - onChange: (v) => updateLight({ id, phi: v }), - }, - [`theta ~${id}`]: { - label: "Theta", - value: theta ?? 1, - step: 0.1, - min: 0, - max: Math.PI * 2, - onChange: (v) => updateLight({ id, theta: v }), - }, +export function LightListItem({ + lightAtom, +}: { + lightAtom: PrimitiveAtom; +}) { + const [light, setLight] = useAtom(lightAtom); + const isSolo = useAtomValue(isSoloAtom); + const toggleSolo = useSetAtom(toggleSoloAtom); + const toggleSelection = useSetAtom(toggleLightSelectionAtom); + const duplicateLight = useSetAtom(duplicateLightAtom); + const deleteLight = useSetAtom(deleteLightAtom); - [`type ~${id}`]: { - label: "Type", - value: type ?? "#fff", - options: ["solid", "gradient", "noise", "texture"], - onChange: (v) => updateLight({ id, type: v }), - }, + const { id, name, visible, solo, selected } = light; - ...(() => { - if (light.type === "solid") { - return { - [`color ~${id}`]: { - label: "Color", - value: light.color ?? "#fff", - onChange: (v) => updateLight({ id, color: v }), - }, - }; - } else if (light.type === "gradient") { - return { - [`colorA ~${id}`]: { - label: "Color A", - value: light.colorA ?? "#f5c664", - onChange: (v) => updateLight({ id, colorA: v }), - }, - [`colorB ~${id}`]: { - label: "Color B", - value: light.colorB ?? "#ff0000", - onChange: (v) => updateLight({ id, colorB: v }), - }, - [`contrast ~${id}`]: { - label: "Contrast", - value: light.contrast ?? 1, - onChange: (v) => updateLight({ id, contrast: v }), - }, - [`axes ~${id}`]: { - label: "Axes", - value: light.axes ?? "x", - options: ["x", "y"], - onChange: (v) => updateLight({ id, axes: v }), - }, - }; - } else if (light.type === "noise") { - return { - [`colorA ~${id}`]: { - label: "Color A", - value: light.colorA ?? "#f5c664", - onChange: (v) => updateLight({ id, colorA: v }), - }, - [`colorB ~${id}`]: { - label: "Color B", - value: light.colorB ?? "#ff0000", - onChange: (v) => updateLight({ id, colorB: v }), - }, - [`colorC ~${id}`]: { - label: "Color C", - value: light.colorB ?? "#00ff00", - onChange: (v) => updateLight({ id, colorC: v }), - }, - [`colorD ~${id}`]: { - label: "Color D", - value: light.colorD ?? "#0000ff", - onChange: (v) => updateLight({ id, colorD: v }), - }, - [`noiseScale ~${id}`]: { - label: "Noise Scale", - value: light.noiseScale ?? 1, - min: 0, - onChange: (v) => updateLight({ id, noiseScale: v }), - }, - [`noiseType ~${id}`]: { - label: "Noise Type", - value: light.noiseType ?? "perlin", - options: ["perlin", "simplex", "cell", "curl"], - onChange: (v) => updateLight({ id, noiseType: v }), - }, - }; - } else if (light.type === "texture") { - return { - [`map ~${id}`]: { - label: "Map", - value: - textureMaps.find((value) => light.map === value)?.name ?? - "none", - options: [ - "none", - ...textureMaps.map((value) => value.name), - ], - onChange: (v) => { - updateLight({ - id, - map: textureMaps.find((map) => map.name === v), - }); - }, - }, - }; - } else { - return {}; - } - })(), + const toggleVisibility = () => + setLight((old) => ({ ...old, visible: !old.visible })); - [`animate ~${id}`]: { - label: "Animate", - value: light.animate ?? false, - onChange: (v) => updateLight({ id, animate: v }), - }, + const updateLight = (light: Partial) => + setLight((old) => ({ ...old, ...light } as any)); - ...(() => { - if (!light.animate) { - return {}; - } + const { + attributes, + listeners, + setNodeRef, + setActivatorNodeRef, + transform, + transition, + } = useSortable({ id: light.id, transition: null }); - return { - [`animationSpeed ~${id}`]: { - label: "Animation Speed", - value: light.animationSpeed ?? 1, - min: 0, - onChange: (v) => updateLight({ id, animationSpeed: v }), - }, - [`animationRotationIntensity ~${id}`]: { - label: "Rotation Intensity", - value: light.animationRotationIntensity ?? 1, - min: 0, - onChange: (v) => - updateLight({ id, animationRotationIntensity: v }), - }, - [`animationFloatIntensity ~${id}`]: { - label: "Float Intensity", - value: light.animationFloatIntensity ?? 1, - min: 0, - onChange: (v) => - updateLight({ id, animationFloatIntensity: v }), - }, - [`animationFloatingRange ~${id}`]: { - label: "Floating Range", - value: light.animationFloatingRange ?? [0, 2], - min: 0, - max: 2, - onChange: (v) => - updateLight({ id, animationFloatingRange: v }), - }, - }; - })(), - }, - { - color: "yellow", - order: 2, - } - ), - }; - } - }, [ - selectedLightId, - id, - name, - shape, - intensity, - type, - distance, - phi, - theta, - scale, - scaleX, - scaleY, - animate, - ]); + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; return ( - - -
  • { - if (selectedLightId === id) { - clearSelectedLight(); - } else { - setSelectedLightId(id); - } - }} - > - - - - - {name} - - + <> + + + - + + -
  • - + - - - duplicateLightById(id)} - > - Duplicate - - removeLightById(id)} - > - Delete - - - - + + + duplicateLight(light.id)} + > + Duplicate + + deleteLight(light.id)} + > + Delete + + + + + + {selected && ( + + + + )} + ); } diff --git a/src/components/Outliner/LightProperties.tsx b/src/components/Outliner/LightProperties.tsx new file mode 100644 index 0000000..08520d6 --- /dev/null +++ b/src/components/Outliner/LightProperties.tsx @@ -0,0 +1,108 @@ +import { Light } from "../../store"; +import { PrimitiveAtom, useAtom } from "jotai"; +import { useCallback, useEffect, useRef } from "react"; +import { Pane } from "tweakpane"; + +export function LightProperties({ + lightAtom, +}: { + lightAtom: PrimitiveAtom; +}) { + const [light, setLight] = useAtom(lightAtom); + const ref = useRef(null!); + const pane = useRef(null!); + + const handleChange = useCallback( + (e: any) => { + setLight((old) => ({ + ...old, + [e.target.key]: structuredClone(e.value), + ts: Date.now(), + })); + }, + [light.id] + ); + + useEffect(() => { + pane.current?.refresh(); + }, [light.ts]); + + useEffect(() => { + if (!ref.current) { + return; + } + + pane.current = new Pane({ container: ref.current, expanded: true }); + + pane.current.addBinding(light, "name").on("change", handleChange); + + pane.current.addBlade({ view: "separator" }); + + pane.current + .addBinding(light, "scale", { min: 0, step: 0.1 }) + .on("change", handleChange); + pane.current + .addBinding(light, "scaleX", { label: "width", min: 0, step: 0.1 }) + .on("change", handleChange); + pane.current + .addBinding(light, "scaleY", { label: "height", min: 0, step: 0.1 }) + .on("change", handleChange); + pane.current + .addBinding(light, "rotation", { step: 0.1 }) + .on("change", handleChange); + pane.current + .addBinding(light, "latlon", { + x: { min: -1, max: 1, step: 0.01 }, + y: { inverted: true, min: -1, max: 1, step: 0.01 }, + }) + .on("change", handleChange); + pane.current.addBinding(light, "target").on("change", handleChange); + + pane.current.addBlade({ view: "separator" }); + + pane.current.addBinding(light, "color").on("change", handleChange); + pane.current + .addBinding(light, "intensity", { min: 0, step: 0.1 }) + .on("change", handleChange); + pane.current + .addBinding(light, "opacity", { min: 0, max: 1 }) + .on("change", handleChange); + + pane.current.addBlade({ view: "separator" }); + + pane.current.addBinding(light, "type", { readonly: true }); + + if (light.type === "procedural_umbrella") { + pane.current + .addBinding(light, "lightSides", { min: 3, max: 20 }) + .on("change", handleChange); + } + + if (light.type === "procedural_scrim") { + pane.current + .addBinding(light, "lightPosition", { + label: "scrim xy", + x: { min: -1, max: 1 }, + y: { inverted: true, min: -1, max: 1 }, + }) + .on("change", handleChange); + pane.current + .addBinding(light, "lightDistance", { + min: 0.01, + max: 1, + label: "spread", + }) + .on("change", handleChange); + } + + if (light.type === "sky_gradient") { + pane.current.addBinding(light, "color2").on("change", handleChange); + } + + return () => { + pane.current.dispose(); + }; + }, [light.id]); + + return
    ; +} diff --git a/src/components/Outliner/Outliner.tsx b/src/components/Outliner/Outliner.tsx index 97af5ee..b2f6d5d 100644 --- a/src/components/Outliner/Outliner.tsx +++ b/src/components/Outliner/Outliner.tsx @@ -1,21 +1,68 @@ import { PlusIcon } from "@heroicons/react/24/outline"; import * as THREE from "three"; - -import { useStore } from "../../hooks/useStore"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragEndEvent, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { + Camera, + Light, + cameraAtomsAtom, + camerasAtom, + isCommandPaletteOpenAtom, + lightAtomsAtom, + lightIdsAtom, + lightsAtom, + selectedCameraAtom, +} from "../../store"; import { LightListItem } from "./LightListItem"; import { CameraListItem } from "./CameraListItem"; +import { useAtomValue, useSetAtom } from "jotai"; export function Outliner() { - const lights = useStore((state) => state.lights); - const cameras = useStore((state) => state.cameras); - const addLight = useStore((state) => state.addLight); - const addCamera = useStore((state) => state.addCamera); + const lightIds = useAtomValue(lightIdsAtom); + const setLights = useSetAtom(lightsAtom); + const setIsCommandPaletteOpen = useSetAtom(isCommandPaletteOpenAtom); + const lightAtoms = useAtomValue(lightAtomsAtom); + const cameraAtoms = useAtomValue(cameraAtomsAtom); + const setCameras = useSetAtom(camerasAtom); + const currentCamera = useAtomValue(selectedCameraAtom); + const addCamera = (camera: Camera) => + setCameras((cameras) => [...cameras, camera]); + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event; + + if (active && over && active.id !== over.id) { + setLights((lights) => { + const oldIndex = lights.findIndex((light) => light.id === active.id); + const newIndex = lights.findIndex((light) => light.id === over.id); - const selectedCameraId = useStore((state) => state.selectedCameraId); - const currentCamera = cameras.find((c) => c.id === selectedCameraId); + return arrayMove(lights, oldIndex, newIndex); + }); + } + } return ( -
    +

    Cameras @@ -24,10 +71,9 @@ export function Outliner() { className="rounded p-1 -m-1 hover:bg-white/20 transition-colors" onClick={() => { addCamera({ - rotation: [0, 0, 0], - position: [0, 0, 5], ...currentCamera, - name: `Camera ${String.fromCharCode(cameras.length + 65)}`, + selected: false, + name: `Camera ${String.fromCharCode(cameraAtoms.length + 65)}`, id: THREE.MathUtils.generateUUID(), }); }} @@ -37,8 +83,12 @@ export function Outliner() {

      - {cameras.map((camera, index) => ( - + {cameraAtoms.map((cameraAtom, index) => ( + ))}
    @@ -49,36 +99,28 @@ export function Outliner() {
    -
      - {lights.map((light) => ( - - ))} +
        + + + {lightAtoms.map((lightAtom) => ( + + ))} + +
    ); diff --git a/src/components/Properties/index.tsx b/src/components/Properties/index.tsx index ff55f95..c3150b5 100644 --- a/src/components/Properties/index.tsx +++ b/src/components/Properties/index.tsx @@ -1,4 +1,6 @@ -import { Leva } from "leva"; +import tunnel from "tunnel-rat"; + +export const PropertiesPanelTunnel = tunnel(); export function Properties() { return ( @@ -6,23 +8,9 @@ export function Properties() {

    Properties

    +
    - +
    ); diff --git a/src/components/ScenePreview/Cameras.tsx b/src/components/ScenePreview/Cameras.tsx index 81810a9..2993191 100644 --- a/src/components/ScenePreview/Cameras.tsx +++ b/src/components/ScenePreview/Cameras.tsx @@ -1,21 +1,21 @@ import { PerspectiveCamera } from "@react-three/drei"; -import React from "react"; -import { useStore } from "../../hooks/useStore"; +import { camerasAtom } from "../../store"; +import { useAtomValue } from "jotai"; export function Cameras() { - const cameras = useStore((state) => state.cameras); - const selectedCameraId = useStore((state) => state.selectedCameraId); + const cameras = useAtomValue(camerasAtom); return ( <> {cameras.map((camera) => ( + far={100} + /> ))} ); diff --git a/src/components/ScenePreview/Controls.tsx b/src/components/ScenePreview/Controls.tsx index afcfdef..2a90b27 100644 --- a/src/components/ScenePreview/Controls.tsx +++ b/src/components/ScenePreview/Controls.tsx @@ -1,6 +1,7 @@ import { useRef } from "react"; import { OrbitControls } from "@react-three/drei"; -import { useStore } from "../../hooks/useStore"; +import { selectedCameraAtom } from "../../store"; +import { useSetAtom } from "jotai"; export type ControlsProps = { autoRotate: boolean; @@ -8,7 +9,7 @@ export type ControlsProps = { export const Controls = ({ autoRotate }: ControlsProps) => { const controlsRef = useRef>(null); - const updateSelectedCamera = useStore((state) => state.updateSelectedCamera); + const setCamera = useSetAtom(selectedCameraAtom); return ( { enableDamping={false} onEnd={(e) => { if (controlsRef.current) { - updateSelectedCamera({ + setCamera({ position: controlsRef.current.object.position.toArray(), rotation: controlsRef.current.object.rotation.toArray() as [ number, diff --git a/src/components/ScenePreview/Debug.tsx b/src/components/ScenePreview/Debug.tsx index a7ba25d..75f8cb6 100644 --- a/src/components/ScenePreview/Debug.tsx +++ b/src/components/ScenePreview/Debug.tsx @@ -1,7 +1,64 @@ +import { useFrame } from "@react-three/fiber"; +import { useAtom } from "jotai"; +import { useAtomCallback } from "jotai/utils"; import { Perf } from "r3f-perf"; +import { useRef } from "react"; +import * as THREE from "three"; +import { useKeyPress } from "../../hooks/useKeyPress"; +import { debugAtom, pointerAtom } from "../../store"; export function Debug() { + const [debug, setDebug] = useAtom(debugAtom); + + useKeyPress("]", () => { + setDebug((old) => !old); + }); + + const arrowRef = useRef(null); + const polarGridRef = useRef(null); + + const readPointer = useAtomCallback((get) => { + const pointer = get(pointerAtom); + return pointer; + }); + + useFrame(() => { + if (!debug) { + return; + } + + const { point, normal } = readPointer(); + + if (arrowRef.current) { + arrowRef.current.position.copy(point); + arrowRef.current.setDirection(normal); + } + + if (polarGridRef.current) { + polarGridRef.current.position.copy(point); + polarGridRef.current.lookAt( + point.clone().add(normal.clone().multiplyScalar(1)) + ); + polarGridRef.current.rotateX(Math.PI / 2); + polarGridRef.current.updateMatrixWorld(); + } + }); + + if (!debug) { + return null; + } + return ( - + <> + + + + + + ); } diff --git a/src/components/ScenePreview/LoadTextureMaps.tsx b/src/components/ScenePreview/LoadTextureMaps.tsx deleted file mode 100644 index dcef35f..0000000 --- a/src/components/ScenePreview/LoadTextureMaps.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useTexture } from "@react-three/drei"; -import * as THREE from "three"; -import { useStore } from "../../hooks/useStore"; - -export function LoadTextureMaps() { - const setTextureMaps = useStore((state) => state.setTextureMaps); - - useTexture({ checkerboard: "/textures/checkerboard.png" }, (textures) => { - if (Array.isArray(textures)) { - textures.forEach((texture) => { - texture.wrapS = THREE.RepeatWrapping; - texture.wrapT = THREE.RepeatWrapping; - const url = new URL(texture.source.data.currentSrc); - texture.name = url.pathname.split("/").pop() as string; - texture.needsUpdate = true; - }); - setTextureMaps(textures); - } else { - setTextureMaps([textures]); - } - }); - - return null; -} diff --git a/src/components/ScenePreview/index.tsx b/src/components/ScenePreview/index.tsx index b45dde8..bb6854c 100644 --- a/src/components/ScenePreview/index.tsx +++ b/src/components/ScenePreview/index.tsx @@ -1,81 +1,73 @@ -import { Suspense } from "react"; -import { Bvh, PerformanceMonitor, useGLTF } from "@react-three/drei"; -import { Canvas } from "@react-three/fiber"; -import { button, folder, useControls } from "leva"; -import { useStore } from "../../hooks/useStore"; -import { Effects } from "../Effects"; +import { BoltIcon } from "@heroicons/react/24/solid"; +import { Bvh, Environment, PerformanceMonitor } from "@react-three/drei"; +import { Canvas, ThreeEvent } from "@react-three/fiber"; +import { useSetAtom } from "jotai"; +import { PointerEvent, Suspense, useCallback } from "react"; +import { toast } from "sonner"; +import * as THREE from "three"; +import { lightsAtom, pointerAtom } from "../../store"; import { Env } from "../Env"; import { Model } from "../Model"; import { Cameras } from "./Cameras"; import { Controls } from "./Controls"; import { Debug } from "./Debug"; -import { LoadTextureMaps } from "./LoadTextureMaps"; import { Lights } from "./Lights"; -import { toast } from "sonner"; -import { BoltIcon } from "@heroicons/react/24/solid"; export function ScenePreview() { - const mode = useStore((state) => state.mode); - - const [{ ambientLightIntensity, debugMaterial, autoRotate }] = useControls( - () => ({ - Preview: folder( - { - ambientLightIntensity: { - label: "Ambient Intensity", - value: 0.5, - min: 0, - max: 3, - render: () => mode.scene, - }, - debugMaterial: { - label: "Debug Material", - value: false, - render: () => mode.scene, - }, - autoRotate: { - label: "Auto Rotate", - value: false, - render: () => mode.scene, - }, - Screenshot: button(() => { - const canvas = document.querySelector("canvas"); - if (canvas) { - const link = document.createElement("a"); - link.download = "screenshot.png"; - link.href = canvas.toDataURL("image/png", 1); - link.click(); - } - }), - "Upload Model": button(() => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".glb"; - input.onchange = (e) => { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - const data = e.target?.result; - if (typeof data === "string") { - const modelUrl = data; - useGLTF.preload(modelUrl); - useStore.setState({ modelUrl }); - } - }; - reader.readAsDataURL(file); - } - }; - input.click(); - }), - }, - { - order: 1, - color: "limegreen", - collapsed: true, - } - ), - }) + const setLights = useSetAtom(lightsAtom); + + const handleModelClick = useCallback( + (e: ThreeEvent) => { + e.stopPropagation(); + + const cameraPosition = e.camera.position.clone(); + const point = e.point.clone(); + const normal = + e.face?.normal?.clone()?.transformDirection(e.object.matrixWorld) ?? + new THREE.Vector3(0, 0, 1); + + // Reflect the camera position across the normal so that the + // light is visible in the reflection. + const cameraToPoint = point.clone().sub(cameraPosition).normalize(); + const reflected = cameraToPoint.reflect(normal); + + const spherical = new THREE.Spherical().setFromVector3(reflected); + + const lat = THREE.MathUtils.mapLinear(spherical.phi, 0, Math.PI, 1, -1); + const lon = THREE.MathUtils.mapLinear( + spherical.theta, + 0.5 * Math.PI, + -1.5 * Math.PI, + -1, + 1 + ); + + const { x, y, z } = point; + setLights((lights) => + lights.map((l) => ({ + ...l, + target: l.selected ? { x, y, z } : l.target, + latlon: l.selected ? { x: lon, y: lat } : l.latlon, + ts: Date.now(), + })) + ); + }, + [setLights] + ); + + const setPointer = useSetAtom(pointerAtom); + const handleModelPointerMove = useCallback( + (e: ThreeEvent) => { + e.stopPropagation(); + + const point = e.point.clone(); + const normal = + e.face?.normal?.clone()?.transformDirection(e.object.matrixWorld) ?? + new THREE.Vector3(0, 0, 1); + + setPointer({ point, normal }); + }, + [setPointer] ); return ( @@ -87,11 +79,12 @@ export function ScenePreview() { logarithmicDepthBuffer: true, antialias: true, }} + style={{ touchAction: "none" }} > { toast("Switching to low performance mode", { description: @@ -101,26 +94,35 @@ export function ScenePreview() { }} > + - + - - - + + + {/* */} - - - + ); diff --git a/src/hooks/useStore.tsx b/src/hooks/useStore.tsx deleted file mode 100644 index c25fdf3..0000000 --- a/src/hooks/useStore.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import * as THREE from "three"; -import create from "zustand"; -import { persist } from "zustand/middleware"; -import { immer } from "zustand/middleware/immer"; - -export type Camera = { - id: string; - name: string; - position: [number, number, number]; - rotation: [number, number, number]; -}; - -type BaseLight = { - id: string; - name: string; - shape: "rect" | "circle" | "ring"; - intensity: number; - scale: number; - scaleX: number; - scaleY: number; - rotation: number; - distance: number; - phi: number; - theta: number; - target: [number, number, number]; - visible: boolean; - solo: boolean; - opacity: number; - animate: boolean; - animationSpeed?: number; - animationRotationIntensity?: number; - animationFloatIntensity?: number; - animationFloatingRange?: [number, number]; -}; - -type SolidLight = BaseLight & { - type: "solid"; - color: string; -}; - -type GradientLight = BaseLight & { - type: "gradient"; - colorA: string; - colorB: string; - contrast: number; - axes: "x" | "y" | "z"; -}; - -type NoiseLight = BaseLight & { - type: "noise"; - algorithm: "perlin" | "simplex" | "cell" | "curl"; - colorA: string; - colorB: string; - colorC: string; - colorD: string; - noiseScale: number; - noiseType: "perlin" | "simplex" | "cell" | "curl"; -}; - -type TextureLight = BaseLight & { - type: "texture"; - map: THREE.Texture; -}; - -export type Light = SolidLight | GradientLight | NoiseLight | TextureLight; - -type State = { - mode: Record<"scene" | "code" | "hdri", boolean>; - setMode: (mode: State["mode"]) => void; - modelUrl: string; - isSolo: boolean; - textureMaps: THREE.Texture[]; - cameras: Camera[]; - selectedCameraId: string; - lights: Light[]; - selectedLightId: string | null; - setTextureMaps: (maps: THREE.Texture[]) => void; - setSelectedCameraId: (id: string) => void; - resetSelectedCamera: () => void; - updateSelectedCamera: (camera: Partial) => void; - addCamera: (camera: Camera) => void; - setSelectedLightId: (id: string) => void; - clearSelectedLight: () => void; - addLight: (light: Light) => void; - updateLight: (light: Partial) => void; - setLightVisibleById: (id: string, visible: boolean) => void; - toggleLightVisibilityById: (id: string) => void; - duplicateLightById: (id: string) => void; - removeLightById: (id: string) => void; - removeSelectedLight: () => void; - duplicateSelectedLight: () => void; - toggleSoloLightById: (id: string) => void; -}; - -export const useStore = create()( - persist( - immer( - (set, get) => - ({ - mode: { scene: true, hdri: true, code: false }, - setMode: (mode) => set({ mode }), - modelUrl: "/911-transformed.glb", - isSolo: false, - textureMaps: [], - setTextureMaps: (maps: THREE.Texture[]) => - set((state) => void (state.textureMaps = maps)), - cameras: [ - { - id: "default", - name: "Default", - position: [0, 0, 5], - rotation: [0, 0, 0], - }, - ], - selectedCameraId: "default", - setSelectedCameraId: (id: string) => set({ selectedCameraId: id }), - resetSelectedCamera: () => { - set({ selectedCameraId: "default" }); - }, - addCamera: (camera: Camera) => - set((state) => void state.cameras.push(camera)), - - updateSelectedCamera: (camera: Partial) => - set((state) => ({ - cameras: state.cameras.map((c: Camera) => - c.id === get().selectedCameraId ? { ...c, ...camera } : c - ), - })), - lights: [ - { - name: `Light A`, - id: THREE.MathUtils.generateUUID(), - shape: "rect", - type: "solid", - color: "#fff", - distance: 4, - phi: Math.PI / 2, - theta: 0, - intensity: 1, - rotation: 0, - scale: 2, - scaleX: 1, - scaleY: 1, - target: [0, 0, 0], - visible: true, - solo: false, - opacity: 1, - animate: false, - }, - ], - selectedLightId: null, - setSelectedLightId: (id: string) => set({ selectedLightId: id }), - clearSelectedLight: () => { - set({ selectedLightId: null }); - }, - addLight: (light: Light) => - set((state) => ({ - lights: [...state.lights, light], - })), - updateLight: (light: Partial) => - set((state) => { - const targetLight = state.lights.find( - (l: Light) => l.id === light.id - ); - - if (!targetLight) { - return; - } - - Object.assign(targetLight, light); - }), - setLightVisibleById: (id: string, visible: boolean) => { - const light = get().lights.find((l) => l.id === id); - if (light) { - set((state) => { - const light = state.lights.find((l: Light) => l.id === id); - if (light) { - light.visible = visible; - } - }); - } - }, - toggleLightVisibilityById: (id: string) => { - const state = get(); - const light = state.lights.find((l) => l.id === id); - if (light) { - state.setLightVisibleById(id, !light.visible); - } - }, - duplicateLightById: (id: string) => { - const state = get(); - const light = state.lights.find((l) => l.id === id); - if (light) { - const newLight = { - ...light, - id: THREE.MathUtils.generateUUID(), - name: `${light.name} (copy)`, - }; - state.addLight(newLight); - } - }, - removeLightById: (id: string) => { - const state = get(); - const light = state.lights.find((l) => l.id === id); - if (light) { - set((state) => ({ - lights: state.lights.filter((l) => l.id !== id), - selectedLightId: - state.selectedLightId === id ? null : state.selectedLightId, - })); - } - }, - removeSelectedLight: () => { - const state = get(); - if (state.selectedLightId) { - state.removeLightById(state.selectedLightId); - } - }, - duplicateSelectedLight: () => { - const state = get(); - if (state.selectedLightId) { - state.duplicateLightById(state.selectedLightId); - } - }, - toggleSoloLightById: (id: string) => { - set((state) => { - const light = state.lights.find((l) => l.id === id); - if (light) { - light.solo = !light.solo; - } - - // Check if any lights are soloed - const soloed = state.lights.some((l) => l.solo); - if (soloed) { - // If so, make all lights invisible except the soloed ones - state.lights.forEach((l) => (l.visible = l.solo)); - state.isSolo = true; - } else { - // If not, make all lights visible - state.lights.forEach((l) => (l.visible = true)); - state.isSolo = false; - } - }); - }, - } as State) - ), - { - name: "env-storage", - version: 4, - getStorage: () => localStorage, - } - ) -); diff --git a/src/index.css b/src/index.css index 964a243..56a04e0 100644 --- a/src/index.css +++ b/src/index.css @@ -8,4 +8,35 @@ body, height: 100%; width: 100%; margin: 0; + overscroll-behavior: none; +} + +:root { + --tp-base-background-color: theme("colors.neutral.900"); + --tp-base-shadow-color: transparent; + + --tp-button-background-color: theme("colors.blue.500"); + --tp-button-background-color-active: theme("colors.blue.600"); + --tp-button-background-color-focus: theme("colors.blue.400"); + --tp-button-background-color-hover: theme("colors.blue.400"); + --tp-button-foreground-color: theme("colors.white"); + + --tp-container-background-color: hsla(0, 0%, 0%, 0.3); + --tp-container-background-color-active: hsla(0, 0%, 0%, 0.6); + --tp-container-background-color-focus: hsla(0, 0%, 0%, 0.5); + --tp-container-background-color-hover: hsla(0, 0%, 0%, 0.4); + --tp-container-foreground-color: hsla(0, 0%, 100%, 0.5); + + --tp-groove-foreground-color: theme("colors.neutral.800"); + + --tp-input-background-color: theme("colors.neutral.800"); + --tp-input-background-color-active: theme("colors.neutral.900"); + --tp-input-background-color-focus: theme("colors.neutral.700"); + --tp-input-background-color-hover: theme("colors.neutral.700"); + --tp-input-foreground-color: theme("colors.neutral.400"); + + --tp-label-foreground-color: theme("colors.neutral.400"); + + --tp-monitor-background-color: theme("colors.neutral.800"); + --tp-monitor-foreground-color: theme("colors.neutral.400"); } diff --git a/src/store.tsx b/src/store.tsx new file mode 100644 index 0000000..e3faf10 --- /dev/null +++ b/src/store.tsx @@ -0,0 +1,274 @@ +import * as THREE from "three"; +import { atom } from "jotai"; +import { splitAtom, atomWithStorage } from "jotai/utils"; + +export type Camera = { + id: string; + name: string; + selected: boolean; + position: [number, number, number]; + rotation: [number, number, number]; +}; + +type BaseLight = { + id: string; + ts: number; + name: string; + + shape: "rect" | "circle" | "ring"; + intensity: number; + opacity: number; + + scale: number; + scaleX: number; + scaleY: number; + rotation: number; + + latlon: { x: number; y: number }; + target: { x: number; y: number; z: number }; + + selected: boolean; + visible: boolean; + solo: boolean; + + animate: boolean; + animationSpeed?: number; + animationRotationIntensity?: number; + animationFloatIntensity?: number; + animationFloatingRange?: [number, number]; +}; + +export type TextureLight = BaseLight & { + type: "texture"; + color: string; + map: string; +}; + +export type ProceduralScrimLight = BaseLight & { + type: "procedural_scrim"; + color: string; + lightPosition: { x: number; y: number }; + lightDistance: number; +}; + +export type ProceduralUmbrellaLight = BaseLight & { + type: "procedural_umbrella"; + color: string; + lightSides: number; +}; + +export type SkyGradientLight = BaseLight & { + type: "sky_gradient"; + color: string; + color2: string; +}; + +export type Light = + | TextureLight + | ProceduralScrimLight + | ProceduralUmbrellaLight + | SkyGradientLight; + +export const debugAtom = atom(false); + +export const modeAtom = atomWithStorage("mode", { + scene: true, + hdri: true, + code: false, +}); + +export const activeModesAtom = atom((get) => { + const mode = get(modeAtom); + return Object.keys(mode).filter((key) => mode[key as keyof typeof mode]); +}); + +export const modelUrlAtom = atom("/911-transformed.glb"); + +export const isCommandPaletteOpenAtom = atom(false); + +export const pointerAtom = atom({ + point: new THREE.Vector3(), + normal: new THREE.Vector3(), +}); + +export const lightsAtom = atomWithStorage("lights", [ + { + name: `Light A`, + id: THREE.MathUtils.generateUUID(), + ts: Date.now(), + shape: "rect", + type: "procedural_scrim", + color: "#fff", + latlon: { x: 0, y: 0 }, + intensity: 1, + rotation: 0, + scale: 2, + scaleX: 1, + scaleY: 1, + target: { x: 0, y: 0, z: 0 }, + selected: false, + visible: true, + solo: false, + opacity: 1, + animate: false, + lightDistance: 0.3, + lightPosition: { x: 0, y: 0 }, + }, +]); + +export const lightIdsAtom = atom((get) => get(lightsAtom).map((l) => l.id)); + +export const lightAtomsAtom = splitAtom(lightsAtom); + +export const isSoloAtom = atom((get) => { + const lights = get(lightsAtom); + return lights.length > 0 && lights.some((l) => l.solo); +}); + +export const isLightSelectedAtom = atom((get) => { + const lights = get(lightsAtom); + return lights.length > 0 && lights.some((l) => l.selected); +}); + +export const selectLightAtom = atom(null, (get, set, lightId: Light["id"]) => { + set(lightsAtom, (lights) => + lights.map((l) => ({ + ...l, + selected: l.id === lightId, + })) + ); +}); + +export const toggleSoloAtom = atom(null, (get, set, lightId: Light["id"]) => { + const lights = get(lightsAtom); + const light = lights.find((l) => l.id === lightId)!; + const isSolo = get(isSoloAtom); + + if (isSolo && light.solo) { + set( + lightsAtom, + lights.map((l) => ({ + ...l, + solo: false, + visible: true, + })) + ); + } else { + set( + lightsAtom, + lights.map((l) => ({ + ...l, + solo: l.id === lightId, + visible: l.id === lightId, + selected: l.id === lightId, + })) + ); + } +}); + +export const toggleLightSelectionAtom = atom( + null, + (get, set, lightId: Light["id"]) => { + set(lightsAtom, (lights) => + lights.map((l) => ({ + ...l, + selected: l.id === lightId ? !l.selected : false, + })) + ); + } +); + +export const duplicateLightAtom = atom( + null, + (get, set, lightId: Light["id"]) => { + const lights = get(lightsAtom); + const light = lights.find((l) => l.id === lightId)!; + const isSolo = get(isSoloAtom); + const newLight = { + ...structuredClone(light), + visible: isSolo ? false : light.visible, + solo: false, + selected: false, + id: THREE.MathUtils.generateUUID(), + name: `${light.name} (copy)`, + }; + set(lightsAtom, [...lights, newLight]); + } +); + +export const deleteLightAtom = atom(null, (get, set, lightId: Light["id"]) => { + const lights = get(lightsAtom); + const light = lights.find((l) => l.id === lightId)!; + const isSolo = get(isSoloAtom); + + const newLights = lights.filter((l) => l.id !== lightId); + + if (isSolo && light.solo) { + set( + lightsAtom, + newLights.map((l) => ({ + ...l, + solo: false, + visible: true, + })) + ); + } else { + set(lightsAtom, newLights); + } +}); + +export const camerasAtom = atomWithStorage("cameras", [ + { + id: "default", + name: "Default", + selected: true, + position: [0, 0, 5], + rotation: [0, 0, 0], + }, +]); + +export const cameraAtomsAtom = splitAtom(camerasAtom); + +export const selectedCameraAtom = atom( + (get) => { + const cameras = get(camerasAtom); + return cameras.find((c) => c.selected)!; + }, + (get, set, value: Partial) => { + const cameras = get(camerasAtom); + const selectedCamera = cameras.find((c) => c.selected)!; + set( + camerasAtom, + cameras.map((c) => (c.id === selectedCamera.id ? { ...c, ...value } : c)) + ); + } +); + +export const isCameraSelectedAtom = atom((get) => { + const cameras = get(camerasAtom); + return cameras.length > 0 && cameras.some((c) => c.selected); +}); + +export const toggleCameraSelectionAtom = atom( + null, + (get, set, cameraId: Camera["id"]) => { + set(camerasAtom, (cameras) => + cameras.map((c) => ({ + ...c, + selected: c.id === cameraId ? !c.selected : false, + })) + ); + } +); + +export const selectCameraAtom = atom( + null, + (get, set, cameraId: Camera["id"]) => { + set(camerasAtom, (cameras) => + cameras.map((c) => ({ + ...c, + selected: c.id === cameraId, + })) + ); + } +); diff --git a/src/utils/coordinates.ts b/src/utils/coordinates.ts new file mode 100644 index 0000000..bb2acbb --- /dev/null +++ b/src/utils/coordinates.ts @@ -0,0 +1,16 @@ +import * as THREE from "three"; + +export function latlonToPhiTheta(latlon: { x: number; y: number }): { + phi: number; + theta: number; +} { + const phi = THREE.MathUtils.mapLinear(latlon.y, -1, 1, Math.PI, 0); + const theta = THREE.MathUtils.mapLinear( + latlon.x, + -1, + 1, + 0.5 * Math.PI, + -1.5 * Math.PI + ); + return { phi, theta }; +} diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 2c527f2..9d973e9 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -4,7 +4,50 @@ const plugin = require("tailwindcss/plugin"); module.exports = { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { - extend: {}, + extend: { + gridColumn: { + "span-13": "span 13 / span 13", + "span-14": "span 14 / span 14", + "span-15": "span 15 / span 15", + "span-16": "span 16 / span 16", + "span-17": "span 17 / span 17", + "span-18": "span 18 / span 18", + "span-19": "span 19 / span 19", + "span-20": "span 20 / span 20", + "span-21": "span 21 / span 21", + "span-22": "span 22 / span 22", + "span-23": "span 23 / span 23", + "span-24": "span 24 / span 24", + }, + gridColumnStart: { + 13: "13", + 14: "14", + 15: "15", + 16: "16", + 17: "17", + 18: "18", + 19: "19", + 20: "20", + 21: "21", + 22: "22", + 23: "23", + 24: "24", + }, + gridColumnEnd: { + 13: "13", + 14: "14", + 15: "15", + 16: "16", + 17: "17", + 18: "18", + 19: "19", + 20: "20", + 21: "21", + 22: "22", + 23: "23", + 24: "24", + }, + }, }, plugins: [ plugin(({ addVariant }) => { diff --git a/vite.config.ts b/vite.config.ts index b1b5f91..c2f13ca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,11 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import jotaiDebugLabel from "jotai/babel/plugin-debug-label"; +import jotaiReactRefresh from "jotai/babel/plugin-react-refresh"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()] -}) + plugins: [ + react({ babel: { plugins: [jotaiDebugLabel, jotaiReactRefresh] } }), + ], +}); diff --git a/yarn.lock b/yarn.lock index 21e7c09..6dddcde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -347,6 +347,55 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/accessibility@npm:^3.0.0": + version: 3.0.1 + resolution: "@dnd-kit/accessibility@npm:3.0.1" + dependencies: + tslib: ^2.0.0 + peerDependencies: + react: ">=16.8.0" + checksum: 0afc2c0fce9a1c107453620ca0da1778f182d340e74ffbc6e369ef0ac8943cafb929d3a6c0891d9b915aa23b2b92137ff4fad958f43118466586d8129a3359d5 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.0.8": + version: 6.0.8 + resolution: "@dnd-kit/core@npm:6.0.8" + dependencies: + "@dnd-kit/accessibility": ^3.0.0 + "@dnd-kit/utilities": ^3.2.1 + tslib: ^2.0.0 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: abe48ff7395f84fd8c15e6c8b13da4df153dc1f1076096d783acd0c25539516c77e4854ea59be6621dde55739cb0df1d62924ad069df3267fe05ad90ef729b2f + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^7.0.2": + version: 7.0.2 + resolution: "@dnd-kit/sortable@npm:7.0.2" + dependencies: + "@dnd-kit/utilities": ^3.2.0 + tslib: ^2.0.0 + peerDependencies: + "@dnd-kit/core": ^6.0.7 + react: ">=16.8.0" + checksum: 4ce705aceb15766a0deefe25a9d95a87e9413c3fb9088ea3eb0962e57f844895000117fcec7c0944a0d4ae4e1e889cfa69e3d3778164d4d23115fb1edb218283 + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.1": + version: 3.2.1 + resolution: "@dnd-kit/utilities@npm:3.2.1" + dependencies: + tslib: ^2.0.0 + peerDependencies: + react: ">=16.8.0" + checksum: 038fd5cc1328bf4c9dca17cd48046e5a687bbf9d904c7197f851aab869ab52d9dee2734b2e255256fd6158245acd00063a23deed962c7673c0fadfbf061f04ca + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm64@npm:0.17.19" @@ -663,25 +712,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/popper@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/popper@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - csstype: ^3.0.4 - checksum: f362a9fb8ed8db7ae9d697c9847d7b709d77e8630964c4bf6de458cac14822bab9c0e35aa3f2ea600a556cae49d025941a8bcee689b8a5fa18f8a0084576640c - languageName: node - linkType: hard - -"@radix-ui/primitive@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/primitive@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - checksum: 5f721bcfebb2482fc2d034d2782219f4b1035977d3a1bd854719ff07c82fb545083ff1247a987ea0218109c5801375724f60910b0c71f7bb78ea0ab21b2bcb26 - languageName: node - linkType: hard - "@radix-ui/primitive@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/primitive@npm:1.0.0" @@ -691,18 +721,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-arrow@npm:0.1.3": - version: 0.1.3 - resolution: "@radix-ui/react-arrow@npm:0.1.3" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-primitive": 0.1.3 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: d8dcb858454653f99f63e41993b9073bbc7b617e4eff6e271a722fc3f19f063e7e90ef549b132fd8dd4f0c4e8b91669f602863e9850799e71fb3be1f2fd9835d - languageName: node - linkType: hard - "@radix-ui/react-arrow@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-arrow@npm:1.0.0" @@ -748,17 +766,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-compose-refs@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-compose-refs@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: d1455577b2afee141998e847890e8f5ba5cb17aa58ba699f9abe21c7948e2435bbda28f7f7efe825ca200c66bcaf095ff4b93553778d599cba3f611c97cd222e - languageName: node - linkType: hard - "@radix-ui/react-compose-refs@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-compose-refs@npm:1.0.0" @@ -788,17 +795,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-context@npm:0.1.1": - version: 0.1.1 - resolution: "@radix-ui/react-context@npm:0.1.1" - dependencies: - "@babel/runtime": ^7.13.10 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 85ed35b6e386706bc3a8d21ff7e2a55d0f452fd8ab89f6c9a6c2e271e390c8788800517589d5606a3bfbcca08741fbcb4b6c695c466a284ae35957d92620c467 - languageName: node - linkType: hard - "@radix-ui/react-context@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-context@npm:1.0.0" @@ -899,18 +895,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-id@npm:0.1.4": - version: 0.1.4 - resolution: "@radix-ui/react-id@npm:0.1.4" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-use-layout-effect": 0.1.0 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 09ed338c1039e5ce6cb1c7d5c288643aa4e0dbf0a3066e9da6cd468893061907de7346a97590b63a9aab42707bf960be2c7314c425269af33209a636297c6184 - languageName: node - linkType: hard - "@radix-ui/react-id@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-id@npm:1.0.0" @@ -953,25 +937,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-popper@npm:0.1.3": - version: 0.1.3 - resolution: "@radix-ui/react-popper@npm:0.1.3" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/popper": 0.1.0 - "@radix-ui/react-arrow": 0.1.3 - "@radix-ui/react-compose-refs": 0.1.0 - "@radix-ui/react-context": 0.1.1 - "@radix-ui/react-primitive": 0.1.3 - "@radix-ui/react-use-rect": 0.1.1 - "@radix-ui/react-use-size": 0.1.0 - "@radix-ui/rect": 0.1.1 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: f057a1c58372ca9c1877facb2284cc135b48467ef7a835ca9ce02d3ecd732316a1cfd90aa28db75684a7b6be015316d8af435daf793c76d778041a2efcd4a556 - languageName: node - linkType: hard - "@radix-ui/react-popper@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-popper@npm:1.0.0" @@ -993,20 +958,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-portal@npm:0.1.3": - version: 0.1.3 - resolution: "@radix-ui/react-portal@npm:0.1.3" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-primitive": 0.1.3 - "@radix-ui/react-use-layout-effect": 0.1.0 - peerDependencies: - react: ^16.8 || ^17.0 - react-dom: ^16.8 || ^17.0 - checksum: 56836fdff1011c6e68baa948c89db2a9f3de2eaa54e9dff5887a1c78769f3ec66bc37c5b2f231f832ce76e82cb3528588b1e321f1bb9bc30ce4e9f7fd30acff0 - languageName: node - linkType: hard - "@radix-ui/react-portal@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-portal@npm:1.0.0" @@ -1020,33 +971,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-portal@npm:^0.1.3": - version: 0.1.4 - resolution: "@radix-ui/react-portal@npm:0.1.4" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-primitive": 0.1.4 - "@radix-ui/react-use-layout-effect": 0.1.0 - peerDependencies: - react: ^16.8 || ^17.0 - react-dom: ^16.8 || ^17.0 - checksum: fb047d56c46711ed1d5146b21f9b77614556d578e55d8c3a39c5014e2883796091a3baeb3d3f8642209a0ff062bf6fd175a666f6f35dd154492ce06761824df3 - languageName: node - linkType: hard - -"@radix-ui/react-presence@npm:0.1.1": - version: 0.1.1 - resolution: "@radix-ui/react-presence@npm:0.1.1" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-compose-refs": 0.1.0 - "@radix-ui/react-use-layout-effect": 0.1.0 - peerDependencies: - react: ">=16.8" - checksum: b8911eb908111135b585fc09500b9582039a53166bf1e59103df083534da5c8d7277c080bd45c270ae70510d41046a15c9ac357094b8588d26eacf9102de1dc6 - languageName: node - linkType: hard - "@radix-ui/react-presence@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-presence@npm:1.0.0" @@ -1061,30 +985,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-primitive@npm:0.1.3": - version: 0.1.3 - resolution: "@radix-ui/react-primitive@npm:0.1.3" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-slot": 0.1.2 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 2cce826e9ec5afbb0251d4ef0f053b87a4cb64694766a07ac4aebc747e734caf4443bc3b7faaea9a71d475cba0d5699a56b658a8a09c0dd89b2c70174c155028 - languageName: node - linkType: hard - -"@radix-ui/react-primitive@npm:0.1.4": - version: 0.1.4 - resolution: "@radix-ui/react-primitive@npm:0.1.4" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-slot": 0.1.2 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: e7b83dc51565a7a54dfd16296e2aa1639dafe32655e3a3974d29d28497f0e9ec9cdf0ee59bc54a88b2a51eeb307781f01f6fcacb4d6dc84a8e10631ddb6142e5 - languageName: node - linkType: hard - "@radix-ui/react-primitive@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-primitive@npm:1.0.0" @@ -1166,18 +1066,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-slot@npm:0.1.2": - version: 0.1.2 - resolution: "@radix-ui/react-slot@npm:0.1.2" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-compose-refs": 0.1.0 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 216927b9b1dae28328d630f6b2c91f1a424c0b00fb4efcebb7a109fdfc5bceda5cf878dfac5baa8aa441150d4c5263f5a914f2962bbce8375972ae076e4d3b65 - languageName: node - linkType: hard - "@radix-ui/react-slot@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-slot@npm:1.0.0" @@ -1255,43 +1143,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-tooltip@npm:0.1.6": - version: 0.1.6 - resolution: "@radix-ui/react-tooltip@npm:0.1.6" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/primitive": 0.1.0 - "@radix-ui/react-compose-refs": 0.1.0 - "@radix-ui/react-context": 0.1.1 - "@radix-ui/react-id": 0.1.4 - "@radix-ui/react-popper": 0.1.3 - "@radix-ui/react-portal": 0.1.3 - "@radix-ui/react-presence": 0.1.1 - "@radix-ui/react-primitive": 0.1.3 - "@radix-ui/react-slot": 0.1.2 - "@radix-ui/react-use-controllable-state": 0.1.0 - "@radix-ui/react-use-escape-keydown": 0.1.0 - "@radix-ui/react-use-previous": 0.1.0 - "@radix-ui/react-use-rect": 0.1.1 - "@radix-ui/react-visually-hidden": 0.1.3 - peerDependencies: - react: ^16.8 || ^17.0 - react-dom: ^16.8 || ^17.0 - checksum: fa409aec05043906305b176019a8fc5d538af0490b623877c47e9e1fc159adf178306a42212566d009837729a276164fcd5e126a4e2320cea573ba6b3e26c4de - languageName: node - linkType: hard - -"@radix-ui/react-use-callback-ref@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-use-callback-ref@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 5356971123d7bbc66a208eca4709483190d0927a6817089f885d4538cd701a174d76830ba36cfaa6336b340415aaefaddc606a575246b0cbcb4b1f2897075203 - languageName: node - linkType: hard - "@radix-ui/react-use-callback-ref@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-callback-ref@npm:1.0.0" @@ -1303,18 +1154,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-use-controllable-state@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-use-controllable-state@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-use-callback-ref": 0.1.0 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 2ddd05854af227b74e8b4a76d0d3c49e83b481b059fad68b769f76b46faa4db8eeb68e1ccf15cf6c4c54a89e6debc6440ee492ccac64570bdf12173e49b2fddc - languageName: node - linkType: hard - "@radix-ui/react-use-controllable-state@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-controllable-state@npm:1.0.0" @@ -1327,18 +1166,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-use-escape-keydown@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-use-escape-keydown@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-use-callback-ref": 0.1.0 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: b92769ecf49eac072c95898e230f9066385b6623d5af8d8f6a322a84bac4bcfab149eb3321fc363dad1d8b1b9706dcdde461a5423bc77f4afffba346b2f11ea3 - languageName: node - linkType: hard - "@radix-ui/react-use-escape-keydown@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-escape-keydown@npm:1.0.0" @@ -1351,17 +1178,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-use-layout-effect@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-use-layout-effect@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: d8be1f97706dec2dcdf98284ad04a115898338dd34f68d61cf9bfda87d88c694019576313a235202b05be3a56ab6453fcee44d651f6b8a502a0cd2dbde153f49 - languageName: node - linkType: hard - "@radix-ui/react-use-layout-effect@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-layout-effect@npm:1.0.0" @@ -1373,29 +1189,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-use-previous@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-use-previous@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 3189d18c5c37bb94c576bb71008c07a2ee935b157c1f94ba7ded0e85df5926a06deb966bc39dfeea1d3f96a80d9504ade36d2e34dbca382767e07a4eea09cfed - languageName: node - linkType: hard - -"@radix-ui/react-use-rect@npm:0.1.1": - version: 0.1.1 - resolution: "@radix-ui/react-use-rect@npm:0.1.1" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/rect": 0.1.1 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: aacf81074482e71661a61bbc14e2bc4a227903e0461465a25dd4b36be0a2eebc6b326ad2f1cd90240d74f6e795cfd72fed0433d94b61f8c275fd75626405946a - languageName: node - linkType: hard - "@radix-ui/react-use-rect@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-rect@npm:1.0.0" @@ -1408,17 +1201,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-use-size@npm:0.1.0": - version: 0.1.0 - resolution: "@radix-ui/react-use-size@npm:0.1.0" - dependencies: - "@babel/runtime": ^7.13.10 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 29134b0caf3109e4c8c5f6b9b6d6c1dd0da4900c899ac4f7e7efecd903f06b8ffd6cf9e42bef464e3d7788bbdaa569b1ea0ff4920d2e93d843d53ce6b3815628 - languageName: node - linkType: hard - "@radix-ui/react-use-size@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-size@npm:1.0.0" @@ -1431,27 +1213,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-visually-hidden@npm:0.1.3": - version: 0.1.3 - resolution: "@radix-ui/react-visually-hidden@npm:0.1.3" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/react-primitive": 0.1.3 - peerDependencies: - react: ^16.8 || ^17.0 - checksum: 4434bc95244c8224af5eb4e37f6278da96df1bce05e85ea7c4fecef1ed9c0ed3252e1269c7f979b9c115a9c75ad057a028078abc42ebebddcde0b97a0e06f92d - languageName: node - linkType: hard - -"@radix-ui/rect@npm:0.1.1": - version: 0.1.1 - resolution: "@radix-ui/rect@npm:0.1.1" - dependencies: - "@babel/runtime": ^7.13.10 - checksum: 6f781fe3f6546930a69de7f3763593c1fbaffd17f03ec613c78946344769c157ebf4d8a5e4eb36e31c7ff51fbff6f7e9b27a3e9f1f411fc6a4528f439b2fba96 - languageName: node - linkType: hard - "@radix-ui/rect@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/rect@npm:1.0.0" @@ -1618,7 +1379,7 @@ __metadata: languageName: node linkType: hard -"@stitches/react@npm:1.2.8, @stitches/react@npm:^1.2.8": +"@stitches/react@npm:^1.2.8": version: 1.2.8 resolution: "@stitches/react@npm:1.2.8" peerDependencies: @@ -1634,6 +1395,13 @@ __metadata: languageName: node linkType: hard +"@tweakpane/core@npm:^2.0.0": + version: 2.0.0 + resolution: "@tweakpane/core@npm:2.0.0" + checksum: c92cb9d1d12717e5d7eea1e8440b63ef1edeff3cd0304fd1a23229ef79348951c9a2bea13a15d612330d22dca690534cd342d7823135ab8acee90222523ccc3d + languageName: node + linkType: hard + "@tweenjs/tween.js@npm:~18.6.4": version: 18.6.4 resolution: "@tweenjs/tween.js@npm:18.6.4" @@ -1748,13 +1516,6 @@ __metadata: languageName: node linkType: hard -"@use-gesture/core@npm:10.2.19": - version: 10.2.19 - resolution: "@use-gesture/core@npm:10.2.19" - checksum: 2ae4d0f3e63e5f679129ff9c3b29fc80fee42e63080063c299fc841deb7ac3608b699e429887da0d6ca212fb3ad1043f93d4dc8d40b30824fa70b35795ecea39 - languageName: node - linkType: hard - "@use-gesture/core@npm:10.2.27": version: 10.2.27 resolution: "@use-gesture/core@npm:10.2.27" @@ -1773,17 +1534,6 @@ __metadata: languageName: node linkType: hard -"@use-gesture/react@npm:^10.2.5": - version: 10.2.19 - resolution: "@use-gesture/react@npm:10.2.19" - dependencies: - "@use-gesture/core": 10.2.19 - peerDependencies: - react: ">= 16.8.0" - checksum: 25835f884382d331d08bb2397e19d14d6319c4969570151317fca7708bec26bd00d0b227e6951913653e324fda353252d854abdcb31b4580172ff8dc608fe31a - languageName: node - linkType: hard - "@utsubo/events@npm:^0.1.7": version: 0.1.7 resolution: "@utsubo/events@npm:0.1.7" @@ -1812,17 +1562,6 @@ __metadata: languageName: node linkType: hard -"@welldone-software/why-did-you-render@npm:^6.2.3": - version: 6.2.3 - resolution: "@welldone-software/why-did-you-render@npm:6.2.3" - dependencies: - lodash: ^4 - peerDependencies: - react: ^16 || ^17 - checksum: 6c012f0a67c94c19cd2e8221438d547990a26b0593b0d1e8fd57084253d7cd305f4904e5bda984fd38416390b35b95fb0c3631fa485bd41b0b317568f080376c - languageName: node - linkType: hard - "abbrev@npm:1": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -1952,20 +1691,6 @@ __metadata: languageName: node linkType: hard -"assign-symbols@npm:^1.0.0": - version: 1.0.0 - resolution: "assign-symbols@npm:1.0.0" - checksum: c0eb895911d05b6b2d245154f70461c5e42c107457972e5ebba38d48967870dee53bcdf6c7047990586daa80fab8dab3cc6300800fbd47b454247fdedd859a2c - languageName: node - linkType: hard - -"attr-accept@npm:^2.2.2": - version: 2.2.2 - resolution: "attr-accept@npm:2.2.2" - checksum: 496f7249354ab53e522510c1dc8f67a1887382187adde4dc205507d2f014836a247073b05e9d9ea51e2e9c7f71b0d2aa21730af80efa9af2d68303e5f0565c4d - languageName: node - linkType: hard - "autoprefixer@npm:^10.4.13": version: 10.4.13 resolution: "autoprefixer@npm:10.4.13" @@ -2229,13 +1954,6 @@ __metadata: languageName: node linkType: hard -"colord@npm:^2.9.2": - version: 2.9.3 - resolution: "colord@npm:2.9.3" - checksum: 95d909bfbcfd8d5605cbb5af56f2d1ce2b323990258fd7c0d2eb0e6d3bb177254d7fb8213758db56bb4ede708964f78c6b992b326615f81a18a6aaf11d64c650 - languageName: node - linkType: hard - "command-score@npm:0.1.2": version: 0.1.2 resolution: "command-score@npm:0.1.2" @@ -2266,13 +1984,6 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:~1.0.0": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 - languageName: node - linkType: hard - "cssesc@npm:^3.0.0": version: 3.0.0 resolution: "cssesc@npm:3.0.0" @@ -2282,7 +1993,7 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2, csstype@npm:^3.0.4": +"csstype@npm:^3.0.2": version: 3.1.0 resolution: "csstype@npm:3.1.0" checksum: 644e986cefab86525f0b674a06889cfdbb1f117e5b7d1ce0fc55b0423ecc58807a1ea42ecc75c4f18999d14fc42d1d255f84662a45003a52bb5840e977eb2ffd @@ -2329,13 +2040,6 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.2": - version: 2.0.3 - resolution: "dequal@npm:2.0.3" - checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 - languageName: node - linkType: hard - "detect-gpu@npm:^5.0.14": version: 5.0.28 resolution: "detect-gpu@npm:5.0.28" @@ -2528,25 +2232,6 @@ __metadata: languageName: node linkType: hard -"extend-shallow@npm:^2.0.1": - version: 2.0.1 - resolution: "extend-shallow@npm:2.0.1" - dependencies: - is-extendable: ^0.1.0 - checksum: 8fb58d9d7a511f4baf78d383e637bd7d2e80843bd9cd0853649108ea835208fb614da502a553acc30208e1325240bb7cc4a68473021612496bb89725483656d8 - languageName: node - linkType: hard - -"extend-shallow@npm:^3.0.0": - version: 3.0.2 - resolution: "extend-shallow@npm:3.0.2" - dependencies: - assign-symbols: ^1.0.0 - is-extendable: ^1.0.1 - checksum: a920b0cd5838a9995ace31dfd11ab5e79bf6e295aa566910ce53dff19f4b1c0fda2ef21f26b28586c7a2450ca2b42d97bd8c0f5cec9351a819222bf861e02461 - languageName: node - linkType: hard - "fast-glob@npm:^3.2.12": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" @@ -2576,15 +2261,6 @@ __metadata: languageName: node linkType: hard -"file-selector@npm:^0.5.0": - version: 0.5.0 - resolution: "file-selector@npm:0.5.0" - dependencies: - tslib: ^2.0.3 - checksum: f95a06938123a2b765d136a4430cc8b19165f06a53e7ae1dcca4947716d61e9181453fcfeb2358c2660cbcecf96d7334f0528ba60071fed81f8bd358ea08454a - languageName: node - linkType: hard - "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -2594,13 +2270,6 @@ __metadata: languageName: node linkType: hard -"for-in@npm:^1.0.2": - version: 1.0.2 - resolution: "for-in@npm:1.0.2" - checksum: 09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d - languageName: node - linkType: hard - "fraction.js@npm:^4.2.0": version: 4.2.0 resolution: "fraction.js@npm:4.2.0" @@ -2680,13 +2349,6 @@ __metadata: languageName: node linkType: hard -"get-value@npm:^2.0.6": - version: 2.0.6 - resolution: "get-value@npm:2.0.6" - checksum: 5c3b99cb5398ea8016bf46ff17afc5d1d286874d2ad38ca5edb6e87d75c0965b0094cb9a9dddef2c59c23d250702323539a7fbdd870620db38c7e7d7ec87c1eb - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -2746,69 +2408,6 @@ __metadata: languageName: node linkType: hard -"glsl-token-assignments@npm:^2.0.0": - version: 2.0.2 - resolution: "glsl-token-assignments@npm:2.0.2" - checksum: efd6051cfd0e5dc4749cc05530e79c42b2396685345695d1232ab3904011e65f117110a2ef7e92a06bc687abf6182f4e90b6b51cc4ab20147aafcc57f724ecb5 - languageName: node - linkType: hard - -"glsl-token-depth@npm:^1.1.0": - version: 1.1.2 - resolution: "glsl-token-depth@npm:1.1.2" - checksum: 97fff701eef20c2ef4552885f060dbf05b307f59b9f1637ddd73c3d5e7d3cc5b4851123706be9f2590042566132f5175ae86a82576bdcfa1edd4625c58d6843c - languageName: node - linkType: hard - -"glsl-token-descope@npm:^1.0.2": - version: 1.0.2 - resolution: "glsl-token-descope@npm:1.0.2" - dependencies: - glsl-token-assignments: ^2.0.0 - glsl-token-depth: ^1.1.0 - glsl-token-properties: ^1.0.0 - glsl-token-scope: ^1.1.0 - checksum: a0d578d5e71178cd5679504a94a60e0811980f46fe6b3cb018bb165530faa75ffcb61b62a1984052223cf2455e36c08f9aa72cf3fdce419aac5d8844ec84cf5a - languageName: node - linkType: hard - -"glsl-token-functions@npm:^1.0.1": - version: 1.0.1 - resolution: "glsl-token-functions@npm:1.0.1" - checksum: 65801ee69830e429f28484181b0ad081c79f2f5c4913073bf0f7c50339de1da2604a82c3b5d29509638cf5a1b302f9425ce91d8e87c56ec1331c5c22f4b2a73c - languageName: node - linkType: hard - -"glsl-token-properties@npm:^1.0.0": - version: 1.0.1 - resolution: "glsl-token-properties@npm:1.0.1" - checksum: 9b4d1caf02d52f6407479bcd3e780133d6952ba6ae0d85ccd4f3de9ead061a173da0820b0238a0e721ae75370b645152d468bc24eb6f1fd37b5000c500d97cd4 - languageName: node - linkType: hard - -"glsl-token-scope@npm:^1.1.0": - version: 1.1.2 - resolution: "glsl-token-scope@npm:1.1.2" - checksum: d62812c81a399d7bdd001ce4414293e508dbd78d480b1984190c8d3243c14817c34109893a71503a50ef09de28e4b0c0124be1979292aba5df3f0207eace1b70 - languageName: node - linkType: hard - -"glsl-token-string@npm:^1.0.1": - version: 1.0.1 - resolution: "glsl-token-string@npm:1.0.1" - checksum: 3260c1486b620277396ecb92b13434764eddcd59330ffb7a25d0e5fc2750fbd4330899e2acb5ab36408ea7451f3e103418ca0430b4c6a225a7e5f318b5028fda - languageName: node - linkType: hard - -"glsl-tokenizer@npm:^2.1.5": - version: 2.1.5 - resolution: "glsl-tokenizer@npm:2.1.5" - dependencies: - through2: ^0.6.3 - checksum: daf70e91c66a3143fe0b22be18a0f8cc965d7b81f73a58b14d55d08593bdcc3f996996549bda78b4cc822d7fe8c216aaeaab71f2695d802fb79fc9e89fb507d3 - languageName: node - linkType: hard - "graceful-fs@npm:^4.2.6": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" @@ -2844,12 +2443,15 @@ __metadata: resolution: "hdri-editor@workspace:." dependencies: "@derschmale/io-rgbe": ^0.1.1 + "@dnd-kit/core": ^6.0.8 + "@dnd-kit/sortable": ^7.0.2 "@heroicons/react": ^2.0.18 "@radix-ui/react-context-menu": ^1.0.0 "@radix-ui/react-toolbar": ^1.0.2 "@react-three/drei": ^9.74.8 "@react-three/fiber": ^8.13.1 "@react-three/postprocessing": ^2.14.9 + "@tweakpane/core": ^2.0.0 "@types/prettier": ^2.7.2 "@types/react": ^18.0.19 "@types/react-dom": ^18.0.6 @@ -2858,9 +2460,7 @@ __metadata: autoprefixer: ^10.4.13 clsx: ^1.2.1 cmdk: ^0.2.0 - immer: ^9.0.15 - lamina: ^1.1.23 - leva: ^0.9.34 + jotai: ^2.3.0 postcss: ^8.4.21 prettier: ^2.8.4 prism-react-renderer: ^1.3.5 @@ -2872,9 +2472,10 @@ __metadata: tailwindcss: ^3.2.6 three: ^0.153.0 three-stdlib: ^2.23.9 + tunnel-rat: ^0.1.2 + tweakpane: ^4.0.0 typescript: ^5.1.3 vite: ^4.3.9 - zustand: ^4.1.1 languageName: unknown linkType: soft @@ -2924,13 +2525,6 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.15": - version: 9.0.15 - resolution: "immer@npm:9.0.15" - checksum: 92e3d63e810e3c3c2bb61b70c45443e37ef983ad12924e3edaf03725ae5979618f5b473439bb3bb4a8c4769f25132f18dec10ea15c40f0b20da5691ff96ff611 - languageName: node - linkType: hard - "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -2962,7 +2556,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:~2.0.1": +"inherits@npm:2, inherits@npm:^2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -3003,22 +2597,6 @@ __metadata: languageName: node linkType: hard -"is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1": - version: 0.1.1 - resolution: "is-extendable@npm:0.1.1" - checksum: 3875571d20a7563772ecc7a5f36cb03167e9be31ad259041b4a8f73f33f885441f778cee1f1fe0085eb4bc71679b9d8c923690003a36a6a5fdf8023e6e3f0672 - languageName: node - linkType: hard - -"is-extendable@npm:^1.0.0, is-extendable@npm:^1.0.1": - version: 1.0.1 - resolution: "is-extendable@npm:1.0.1" - dependencies: - is-plain-object: ^2.0.4 - checksum: db07bc1e9de6170de70eff7001943691f05b9d1547730b11be01c0ebfe67362912ba743cf4be6fd20a5e03b4180c685dad80b7c509fe717037e3eee30ad8e84f - languageName: node - linkType: hard - "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -3056,22 +2634,6 @@ __metadata: languageName: node linkType: hard -"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4": - version: 2.0.4 - resolution: "is-plain-object@npm:2.0.4" - dependencies: - isobject: ^3.0.1 - checksum: 2a401140cfd86cabe25214956ae2cfee6fbd8186809555cd0e84574f88de7b17abacb2e477a6a658fa54c6083ecbda1e6ae404c7720244cd198903848fca70ca - languageName: node - linkType: hard - -"isarray@npm:0.0.1": - version: 0.0.1 - resolution: "isarray@npm:0.0.1" - checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4 - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -3079,13 +2641,6 @@ __metadata: languageName: node linkType: hard -"isobject@npm:^3.0.1": - version: 3.0.1 - resolution: "isobject@npm:3.0.1" - checksum: db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 - languageName: node - linkType: hard - "its-fine@npm:^1.0.6": version: 1.0.8 resolution: "its-fine@npm:1.0.8" @@ -3097,6 +2652,21 @@ __metadata: languageName: node linkType: hard +"jotai@npm:^2.3.0": + version: 2.3.0 + resolution: "jotai@npm:2.3.0" + peerDependencies: + "@types/react": ">=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 21df579532a61e9b745584a17252560ca7267af363e4d9ea3283a1f1290aded33b33d96c5748b09f3e7c23fceae02055a8093647d841b57eb87e566995b89e4a + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -3129,78 +2699,6 @@ __metadata: languageName: node linkType: hard -"lamina@npm:^1.1.22, lamina@npm:^1.1.23": - version: 1.1.23 - resolution: "lamina@npm:1.1.23" - dependencies: - glsl-token-descope: ^1.0.2 - glsl-token-functions: ^1.0.1 - glsl-token-string: ^1.0.1 - glsl-tokenizer: ^2.1.5 - lamina: ^1.1.22 - leva: ^0.9.20 - three-custom-shader-material: ^4.0.0 - peerDependencies: - "@react-three/fiber": ">=8.0" - react: ">=18.0" - react-dom: ">=18.0" - three: ">=0.138" - peerDependenciesMeta: - "@react-three/fiber": - optional: true - react: - optional: true - react-dom: - optional: true - checksum: 2b9b0da0ec25435172ce0dbd0daefbf2262bf34b865c8125def75e32aff5df27a2b408099ba7c955e9d181c6bb7a7f6419c9022c507ebe7ba1fb099748278c31 - languageName: node - linkType: hard - -"leva@npm:^0.9.20": - version: 0.9.31 - resolution: "leva@npm:0.9.31" - dependencies: - "@radix-ui/react-portal": ^0.1.3 - "@radix-ui/react-tooltip": 0.1.6 - "@stitches/react": 1.2.8 - "@use-gesture/react": ^10.2.5 - "@welldone-software/why-did-you-render": ^6.2.3 - colord: ^2.9.2 - dequal: ^2.0.2 - merge-value: ^1.0.0 - react-colorful: ^5.5.1 - react-dropzone: ^12.0.0 - v8n: ^1.3.3 - zustand: ^3.6.9 - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: c3caff4249226c093315ad40fcaa5cdaa2aedca4fe580de526ecdf1260f169c23f6fdcc74665fa43c3c28d83619d1092b02378f265ac1a8c438b24782e9f7b66 - languageName: node - linkType: hard - -"leva@npm:^0.9.34": - version: 0.9.34 - resolution: "leva@npm:0.9.34" - dependencies: - "@radix-ui/react-portal": ^0.1.3 - "@radix-ui/react-tooltip": 0.1.6 - "@stitches/react": 1.2.8 - "@use-gesture/react": ^10.2.5 - colord: ^2.9.2 - dequal: ^2.0.2 - merge-value: ^1.0.0 - react-colorful: ^5.5.1 - react-dropzone: ^12.0.0 - v8n: ^1.3.3 - zustand: ^3.6.9 - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 72461068ac01f174cff0eb4ea3dc4e50736c6e3cf9494bcb777662345dff6af1df512e7884dba803d7f567494cb04618d7104605fe7cf9a92e5b691f32f1f2b5 - languageName: node - linkType: hard - "lil-gui@npm:~0.17.0": version: 0.17.0 resolution: "lil-gui@npm:0.17.0" @@ -3236,7 +2734,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4": +"lodash@npm:4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -3313,18 +2811,6 @@ __metadata: languageName: node linkType: hard -"merge-value@npm:^1.0.0": - version: 1.0.0 - resolution: "merge-value@npm:1.0.0" - dependencies: - get-value: ^2.0.6 - is-extendable: ^1.0.0 - mixin-deep: ^1.2.0 - set-value: ^2.0.0 - checksum: 32c0ecaac8513d43389e979fa3f4bc73f6599ac7440f25721714bc4afc3c78bc55f251817e9ee22e94116791e493a789bf308dfe9e65601e5c8c267be47758f5 - languageName: node - linkType: hard - "merge2@npm:^1.3.0": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -3446,16 +2932,6 @@ __metadata: languageName: node linkType: hard -"mixin-deep@npm:^1.2.0": - version: 1.3.2 - resolution: "mixin-deep@npm:1.3.2" - dependencies: - for-in: ^1.0.2 - is-extendable: ^1.0.1 - checksum: 820d5a51fcb7479f2926b97f2c3bb223546bc915e6b3a3eb5d906dda871bba569863595424a76682f2b15718252954644f3891437cb7e3f220949bed54b1750d - languageName: node - linkType: hard - "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -3824,7 +3300,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.0, prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.0": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -3874,16 +3350,6 @@ __metadata: languageName: node linkType: hard -"react-colorful@npm:^5.5.1": - version: 5.6.1 - resolution: "react-colorful@npm:5.6.1" - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: e432b7cb0df57e8f0bcdc3b012d2e93fcbcb6092c9e0f85654788d5ebfc4442536d8cc35b2418061ba3c4afb8b7788cc101c606d86a1732407921de7a9244c8d - languageName: node - linkType: hard - "react-composer@npm:^5.0.3": version: 5.0.3 resolution: "react-composer@npm:5.0.3" @@ -3907,19 +3373,6 @@ __metadata: languageName: node linkType: hard -"react-dropzone@npm:^12.0.0": - version: 12.1.0 - resolution: "react-dropzone@npm:12.1.0" - dependencies: - attr-accept: ^2.2.2 - file-selector: ^0.5.0 - prop-types: ^15.8.1 - peerDependencies: - react: ">= 16.8" - checksum: 1be37433cf42b8a9f98c8f59678e30fffc1e9b8e3fdb20f3a376557948f727156123ca0a7e45cd3882606184d945ea1139f17da0e1e5ba0b646a23be0ed65fb3 - languageName: node - linkType: hard - "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -4045,18 +3498,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:>=1.0.33-1 <1.1.0-0": - version: 1.0.34 - resolution: "readable-stream@npm:1.0.34" - dependencies: - core-util-is: ~1.0.0 - inherits: ~2.0.1 - isarray: 0.0.1 - string_decoder: ~0.10.x - checksum: 85042c537e4f067daa1448a7e257a201070bfec3dd2706abdbd8ebc7f3418eb4d3ed4b8e5af63e2544d69f88ab09c28d5da3c0b77dc76185fddd189a59863b60 - languageName: node - linkType: hard - "readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" @@ -4248,18 +3689,6 @@ __metadata: languageName: node linkType: hard -"set-value@npm:^2.0.0": - version: 2.0.1 - resolution: "set-value@npm:2.0.1" - dependencies: - extend-shallow: ^2.0.1 - is-extendable: ^0.1.1 - is-plain-object: ^2.0.3 - split-string: ^3.0.1 - checksum: 09a4bc72c94641aeae950eb60dc2755943b863780fcc32e441eda964b64df5e3f50603d5ebdd33394ede722528bd55ed43aae26e9df469b4d32e2292b427b601 - languageName: node - linkType: hard - "signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -4312,15 +3741,6 @@ __metadata: languageName: node linkType: hard -"split-string@npm:^3.0.1": - version: 3.1.0 - resolution: "split-string@npm:3.1.0" - dependencies: - extend-shallow: ^3.0.0 - checksum: ae5af5c91bdc3633628821bde92fdf9492fa0e8a63cf6a0376ed6afde93c701422a1610916f59be61972717070119e848d10dfbbd5024b7729d6a71972d2a84c - languageName: node - linkType: hard - "ssri@npm:^9.0.0": version: 9.0.1 resolution: "ssri@npm:9.0.1" @@ -4364,13 +3784,6 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:~0.10.x": - version: 0.10.31 - resolution: "string_decoder@npm:0.10.31" - checksum: fe00f8e303647e5db919948ccb5ce0da7dea209ab54702894dd0c664edd98e5d4df4b80d6fabf7b9e92b237359d21136c95bf068b2f7760b772ca974ba970202 - languageName: node - linkType: hard - "strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -4455,27 +3868,6 @@ __metadata: languageName: node linkType: hard -"three-custom-shader-material@npm:^4.0.0": - version: 4.0.0 - resolution: "three-custom-shader-material@npm:4.0.0" - dependencies: - glsl-token-functions: ^1.0.1 - glsl-token-string: ^1.0.1 - glsl-tokenizer: ^2.1.5 - object-hash: ^3.0.0 - peerDependencies: - "@react-three/fiber": ">=8.0" - react: ">=18.0" - three: ">=0.140" - peerDependenciesMeta: - "@react-three/fiber": - optional: true - react: - optional: true - checksum: 4803fde8433218c330284491dc548c216b6970da9ffd437d02bb9c711892629bd5b53e4baa3214196a5daf5465e3632112e024adbf1a81a0c8bede3a152dd5d0 - languageName: node - linkType: hard - "three-mesh-bvh@npm:^0.5.23": version: 0.5.24 resolution: "three-mesh-bvh@npm:0.5.24" @@ -4513,16 +3905,6 @@ __metadata: languageName: node linkType: hard -"through2@npm:^0.6.3": - version: 0.6.5 - resolution: "through2@npm:0.6.5" - dependencies: - readable-stream: ">=1.0.33-1 <1.1.0-0" - xtend: ">=4.0.0 <4.1.0-0" - checksum: dfea228e3134a33219a588448847250897a9994a687807dab52f850fac8b4eb1dc18e3b2c1d3d60dd0d78eb492d2032fdf814ac6576ba5b8d5ba0dade29a3544 - languageName: node - linkType: hard - "tiny-inflate@npm:^1.0.3": version: 1.0.3 resolution: "tiny-inflate@npm:1.0.3" @@ -4576,13 +3958,29 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0": +"tslib@npm:^2.0.0, tslib@npm:^2.1.0": version: 2.4.0 resolution: "tslib@npm:2.4.0" checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113 languageName: node linkType: hard +"tunnel-rat@npm:^0.1.2": + version: 0.1.2 + resolution: "tunnel-rat@npm:0.1.2" + dependencies: + zustand: ^4.3.2 + checksum: 9d5975d589db705e7707dcfd2bdb9f1773b014179a7e56a9cd5aaa1824ee28143efcfea30e826f3fccc0a9cb7a8631b4fd490a5849a20893e37e93e122bd9430 + languageName: node + linkType: hard + +"tweakpane@npm:^4.0.0": + version: 4.0.0 + resolution: "tweakpane@npm:4.0.0" + checksum: a7f0839556b7b7f40528cded3c29163f45e319b16f74b4bedd38dda13e161756b1e33f5cd595a2e93bb3f98b069db27e574a1cc58388a53b14e47c3a525a5877 + languageName: node + linkType: hard + "typescript@npm:^5.1.3": version: 5.1.3 resolution: "typescript@npm:5.1.3" @@ -4715,13 +4113,6 @@ __metadata: languageName: node linkType: hard -"v8n@npm:^1.3.3": - version: 1.5.1 - resolution: "v8n@npm:1.5.1" - checksum: 96c8dff9144001da46152f37b9323e2bf9a1f915c6a3f6f5e8683f7a540a6551a18e937267e257f8753da594f33a0b1724770cd50f73e6ea7dc3ceb0510ca72f - languageName: node - linkType: hard - "vite@npm:^4.3.9": version: 4.3.9 resolution: "vite@npm:4.3.9" @@ -4800,7 +4191,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:>=4.0.0 <4.1.0-0, xtend@npm:^4.0.2": +"xtend@npm:^4.0.2": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a @@ -4835,7 +4226,7 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^3.5.13, zustand@npm:^3.6.9, zustand@npm:^3.7.1": +"zustand@npm:^3.5.13, zustand@npm:^3.7.1": version: 3.7.2 resolution: "zustand@npm:3.7.2" peerDependencies: @@ -4847,20 +4238,23 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^4.1.1": - version: 4.1.1 - resolution: "zustand@npm:4.1.1" +"zustand@npm:^4.3.2": + version: 4.4.0 + resolution: "zustand@npm:4.4.0" dependencies: use-sync-external-store: 1.2.0 peerDependencies: + "@types/react": ">=16.8" immer: ">=9.0" react: ">=16.8" peerDependenciesMeta: + "@types/react": + optional: true immer: optional: true react: optional: true - checksum: 03eefb193e2ecb43a761c81cb60f517c2780289dab0f55f2cbdb91400924c6291abb5a007b32ee19787b8f72b6769f891a38b64fd296660c170d632c3182f6e1 + checksum: 37e69eec1b56677a93712e5aa6d0048b55997379919dc0f78f61181f8a58994a6cae064f816f8101f5b1039008d3c1c9d136432a62e0edeb796807cc84cf45ef languageName: node linkType: hard