Compare commits
9 Commits
8ca8f894e4
...
53e68c2279
| Author | SHA1 | Date |
|---|---|---|
|
|
53e68c2279 | |
|
|
06f885e284 | |
|
|
93f421eba4 | |
|
|
37e8f5ccd3 | |
|
|
e08f80fc19 | |
|
|
a5733a4f4e | |
|
|
1210201802 | |
|
|
c80b76e06e | |
|
|
8ef089c833 |
|
|
@ -14,10 +14,12 @@
|
||||||
"@react-three/fiber": "^9.6.1",
|
"@react-three/fiber": "^9.6.1",
|
||||||
"@react-three/rapier": "^2.2.0",
|
"@react-three/rapier": "^2.2.0",
|
||||||
"@types/three": "^0.184.1",
|
"@types/three": "^0.184.1",
|
||||||
|
"blockly": "^12.5.1",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"mobx": "^6.15.4",
|
"mobx": "^6.15.4",
|
||||||
"mobx-react-lite": "^4.1.1",
|
"mobx-react-lite": "^4.1.1",
|
||||||
"mobx-utils": "^6.1.1",
|
"mobx-utils": "^6.1.1",
|
||||||
|
"nipplejs": "^1.0.4",
|
||||||
"npm": "^11.16.0",
|
"npm": "^11.16.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
|
|
|
||||||
329
pnpm-lock.yaml
329
pnpm-lock.yaml
|
|
@ -20,6 +20,9 @@ importers:
|
||||||
'@types/three':
|
'@types/three':
|
||||||
specifier: ^0.184.1
|
specifier: ^0.184.1
|
||||||
version: 0.184.1
|
version: 0.184.1
|
||||||
|
blockly:
|
||||||
|
specifier: ^12.5.1
|
||||||
|
version: 12.5.1
|
||||||
install:
|
install:
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
|
|
@ -32,6 +35,9 @@ importers:
|
||||||
mobx-utils:
|
mobx-utils:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1(mobx@6.15.4)
|
version: 6.1.1(mobx@6.15.4)
|
||||||
|
nipplejs:
|
||||||
|
specifier: ^1.0.4
|
||||||
|
version: 1.0.4
|
||||||
npm:
|
npm:
|
||||||
specifier: ^11.16.0
|
specifier: ^11.16.0
|
||||||
version: 11.16.0
|
version: 11.16.0
|
||||||
|
|
@ -93,6 +99,9 @@ importers:
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
'@asamuzakjp/css-color@3.2.0':
|
||||||
|
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
|
||||||
|
|
||||||
'@babel/code-frame@7.29.7':
|
'@babel/code-frame@7.29.7':
|
||||||
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
@ -164,6 +173,34 @@ packages:
|
||||||
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@csstools/color-helpers@5.1.0':
|
||||||
|
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@csstools/css-calc@2.1.4':
|
||||||
|
resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-parser-algorithms': ^3.0.5
|
||||||
|
'@csstools/css-tokenizer': ^3.0.4
|
||||||
|
|
||||||
|
'@csstools/css-color-parser@3.1.0':
|
||||||
|
resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-parser-algorithms': ^3.0.5
|
||||||
|
'@csstools/css-tokenizer': ^3.0.4
|
||||||
|
|
||||||
|
'@csstools/css-parser-algorithms@3.0.5':
|
||||||
|
resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-tokenizer': ^3.0.4
|
||||||
|
|
||||||
|
'@csstools/css-tokenizer@3.0.4':
|
||||||
|
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.12.0':
|
'@dimforge/rapier3d-compat@0.12.0':
|
||||||
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||||
|
|
||||||
|
|
@ -636,6 +673,10 @@ packages:
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
agent-base@7.1.4:
|
||||||
|
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
ajv@6.15.0:
|
ajv@6.15.0:
|
||||||
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
|
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
|
||||||
|
|
||||||
|
|
@ -654,6 +695,10 @@ packages:
|
||||||
bidi-js@1.0.3:
|
bidi-js@1.0.3:
|
||||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||||
|
|
||||||
|
blockly@12.5.1:
|
||||||
|
resolution: {integrity: sha512-etXLpUtEkcRibHGwIJ4BsvnIzMJJs0C0yPIjE/W0NCtj8ACha/a7Q9n7Ib6+j7N4EzQ0p28YPZMnypi5pNIj1g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
brace-expansion@5.0.6:
|
brace-expansion@5.0.6:
|
||||||
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
@ -695,9 +740,17 @@ packages:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
cssstyle@4.6.0:
|
||||||
|
resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
csstype@3.2.3:
|
csstype@3.2.3:
|
||||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
data-urls@5.0.0:
|
||||||
|
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
|
|
@ -707,6 +760,9 @@ packages:
|
||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decimal.js@10.6.0:
|
||||||
|
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||||
|
|
||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
|
|
@ -723,6 +779,10 @@ packages:
|
||||||
electron-to-chromium@1.5.364:
|
electron-to-chromium@1.5.364:
|
||||||
resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
|
resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
|
||||||
|
|
||||||
|
entities@6.0.1:
|
||||||
|
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -852,6 +912,22 @@ packages:
|
||||||
hls.js@1.6.16:
|
hls.js@1.6.16:
|
||||||
resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==}
|
resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==}
|
||||||
|
|
||||||
|
html-encoding-sniffer@4.0.0:
|
||||||
|
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
http-proxy-agent@7.0.2:
|
||||||
|
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
ieee754@1.2.1:
|
ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
|
||||||
|
|
@ -885,6 +961,9 @@ packages:
|
||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-potential-custom-element-name@1.0.1:
|
||||||
|
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||||
|
|
||||||
is-promise@2.2.2:
|
is-promise@2.2.2:
|
||||||
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
|
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
|
||||||
|
|
||||||
|
|
@ -899,6 +978,15 @@ packages:
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
jsdom@26.1.0:
|
||||||
|
resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
canvas: ^3.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
canvas:
|
||||||
|
optional: true
|
||||||
|
|
||||||
jsesc@3.1.0:
|
jsesc@3.1.0:
|
||||||
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -1006,6 +1094,9 @@ packages:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
lru-cache@10.4.3:
|
||||||
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
|
|
@ -1059,6 +1150,9 @@ packages:
|
||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
nipplejs@1.0.4:
|
||||||
|
resolution: {integrity: sha512-YtvRZDuyWQ+tOj0nUjfnZt4ZQmJVWfK9+yPmV5OXpQ4k4AkOGcSfKvSXbPNkV0ERqc4eakkBSZ7/Wyx1y6h/Sg==}
|
||||||
|
|
||||||
node-addon-api@7.1.1:
|
node-addon-api@7.1.1:
|
||||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||||
|
|
||||||
|
|
@ -1137,6 +1231,9 @@ packages:
|
||||||
- validate-npm-package-name
|
- validate-npm-package-name
|
||||||
- which
|
- which
|
||||||
|
|
||||||
|
nwsapi@2.2.23:
|
||||||
|
resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -1149,6 +1246,9 @@ packages:
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
parse5@7.3.0:
|
||||||
|
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
|
||||||
|
|
||||||
path-exists@4.0.0:
|
path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -1230,11 +1330,21 @@ packages:
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
rrweb-cssom@0.8.0:
|
||||||
|
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2:
|
||||||
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
sass@1.100.0:
|
sass@1.100.0:
|
||||||
resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
|
resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
|
||||||
engines: {node: '>=20.19.0'}
|
engines: {node: '>=20.19.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
saxes@6.0.0:
|
||||||
|
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||||
|
engines: {node: '>=v12.22.7'}
|
||||||
|
|
||||||
scheduler@0.27.0:
|
scheduler@0.27.0:
|
||||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||||
|
|
||||||
|
|
@ -1276,6 +1386,9 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=17.0'
|
react: '>=17.0'
|
||||||
|
|
||||||
|
symbol-tree@3.2.4:
|
||||||
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
|
|
||||||
three-mesh-bvh@0.8.3:
|
three-mesh-bvh@0.8.3:
|
||||||
resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==}
|
resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -1293,6 +1406,21 @@ packages:
|
||||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
tldts-core@6.1.86:
|
||||||
|
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
|
||||||
|
|
||||||
|
tldts@6.1.86:
|
||||||
|
resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
tough-cookie@5.1.2:
|
||||||
|
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
tr46@5.1.1:
|
||||||
|
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
troika-three-text@0.52.4:
|
troika-three-text@0.52.4:
|
||||||
resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
|
resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -1402,12 +1530,33 @@ packages:
|
||||||
yaml:
|
yaml:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
w3c-xmlserializer@5.0.0:
|
||||||
|
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
webgl-constants@1.1.1:
|
webgl-constants@1.1.1:
|
||||||
resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==}
|
resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==}
|
||||||
|
|
||||||
webgl-sdf-generator@1.1.1:
|
webgl-sdf-generator@1.1.1:
|
||||||
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
|
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0:
|
||||||
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
whatwg-encoding@3.1.1:
|
||||||
|
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
|
||||||
|
|
||||||
|
whatwg-mimetype@4.0.0:
|
||||||
|
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
whatwg-url@14.2.0:
|
||||||
|
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
@ -1417,6 +1566,25 @@ packages:
|
||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
ws@8.21.0:
|
||||||
|
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
xml-name-validator@5.0.0:
|
||||||
|
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
xmlchars@2.2.0:
|
||||||
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
|
|
||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
|
|
@ -1468,6 +1636,14 @@ packages:
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
'@asamuzakjp/css-color@3.2.0':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-tokenizer': 3.0.4
|
||||||
|
lru-cache: 10.4.3
|
||||||
|
|
||||||
'@babel/code-frame@7.29.7':
|
'@babel/code-frame@7.29.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
@ -1570,6 +1746,26 @@ snapshots:
|
||||||
'@babel/helper-string-parser': 7.29.7
|
'@babel/helper-string-parser': 7.29.7
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
||||||
|
'@csstools/color-helpers@5.1.0': {}
|
||||||
|
|
||||||
|
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-tokenizer': 3.0.4
|
||||||
|
|
||||||
|
'@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/color-helpers': 5.1.0
|
||||||
|
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-tokenizer': 3.0.4
|
||||||
|
|
||||||
|
'@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-tokenizer': 3.0.4
|
||||||
|
|
||||||
|
'@csstools/css-tokenizer@3.0.4': {}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.12.0': {}
|
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.19.2': {}
|
'@dimforge/rapier3d-compat@0.19.2': {}
|
||||||
|
|
@ -2004,6 +2200,8 @@ snapshots:
|
||||||
|
|
||||||
acorn@8.16.0: {}
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
|
agent-base@7.1.4: {}
|
||||||
|
|
||||||
ajv@6.15.0:
|
ajv@6.15.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
|
|
@ -2021,6 +2219,15 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
require-from-string: 2.0.2
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
|
blockly@12.5.1:
|
||||||
|
dependencies:
|
||||||
|
jsdom: 26.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- canvas
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
brace-expansion@5.0.6:
|
brace-expansion@5.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 4.0.4
|
balanced-match: 4.0.4
|
||||||
|
|
@ -2062,12 +2269,24 @@ snapshots:
|
||||||
shebang-command: 2.0.0
|
shebang-command: 2.0.0
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
|
cssstyle@4.6.0:
|
||||||
|
dependencies:
|
||||||
|
'@asamuzakjp/css-color': 3.2.0
|
||||||
|
rrweb-cssom: 0.8.0
|
||||||
|
|
||||||
csstype@3.2.3: {}
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
data-urls@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
whatwg-mimetype: 4.0.0
|
||||||
|
whatwg-url: 14.2.0
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decimal.js@10.6.0: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
detect-gpu@5.0.70:
|
detect-gpu@5.0.70:
|
||||||
|
|
@ -2080,6 +2299,8 @@ snapshots:
|
||||||
|
|
||||||
electron-to-chromium@1.5.364: {}
|
electron-to-chromium@1.5.364: {}
|
||||||
|
|
||||||
|
entities@6.0.1: {}
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
|
|
@ -2214,6 +2435,28 @@ snapshots:
|
||||||
|
|
||||||
hls.js@1.6.16: {}
|
hls.js@1.6.16: {}
|
||||||
|
|
||||||
|
html-encoding-sniffer@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
whatwg-encoding: 3.1.1
|
||||||
|
|
||||||
|
http-proxy-agent@7.0.2:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
ieee754@1.2.1: {}
|
ieee754@1.2.1: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
@ -2234,6 +2477,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob: 2.1.1
|
is-extglob: 2.1.1
|
||||||
|
|
||||||
|
is-potential-custom-element-name@1.0.1: {}
|
||||||
|
|
||||||
is-promise@2.2.2: {}
|
is-promise@2.2.2: {}
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
@ -2247,6 +2492,33 @@ snapshots:
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
|
jsdom@26.1.0:
|
||||||
|
dependencies:
|
||||||
|
cssstyle: 4.6.0
|
||||||
|
data-urls: 5.0.0
|
||||||
|
decimal.js: 10.6.0
|
||||||
|
html-encoding-sniffer: 4.0.0
|
||||||
|
http-proxy-agent: 7.0.2
|
||||||
|
https-proxy-agent: 7.0.6
|
||||||
|
is-potential-custom-element-name: 1.0.1
|
||||||
|
nwsapi: 2.2.23
|
||||||
|
parse5: 7.3.0
|
||||||
|
rrweb-cssom: 0.8.0
|
||||||
|
saxes: 6.0.0
|
||||||
|
symbol-tree: 3.2.4
|
||||||
|
tough-cookie: 5.1.2
|
||||||
|
w3c-xmlserializer: 5.0.0
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
whatwg-encoding: 3.1.1
|
||||||
|
whatwg-mimetype: 4.0.0
|
||||||
|
whatwg-url: 14.2.0
|
||||||
|
ws: 8.21.0
|
||||||
|
xml-name-validator: 5.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
jsesc@3.1.0: {}
|
jsesc@3.1.0: {}
|
||||||
|
|
||||||
json-buffer@3.0.1: {}
|
json-buffer@3.0.1: {}
|
||||||
|
|
@ -2323,6 +2595,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
@ -2362,6 +2636,8 @@ snapshots:
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
|
nipplejs@1.0.4: {}
|
||||||
|
|
||||||
node-addon-api@7.1.1:
|
node-addon-api@7.1.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -2369,6 +2645,8 @@ snapshots:
|
||||||
|
|
||||||
npm@11.16.0: {}
|
npm@11.16.0: {}
|
||||||
|
|
||||||
|
nwsapi@2.2.23: {}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
|
|
@ -2386,6 +2664,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
|
parse5@7.3.0:
|
||||||
|
dependencies:
|
||||||
|
entities: 6.0.1
|
||||||
|
|
||||||
path-exists@4.0.0: {}
|
path-exists@4.0.0: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
|
|
@ -2463,6 +2745,10 @@ snapshots:
|
||||||
'@rolldown/binding-win32-arm64-msvc': 1.0.2
|
'@rolldown/binding-win32-arm64-msvc': 1.0.2
|
||||||
'@rolldown/binding-win32-x64-msvc': 1.0.2
|
'@rolldown/binding-win32-x64-msvc': 1.0.2
|
||||||
|
|
||||||
|
rrweb-cssom@0.8.0: {}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
sass@1.100.0:
|
sass@1.100.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar: 5.0.0
|
chokidar: 5.0.0
|
||||||
|
|
@ -2471,6 +2757,10 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@parcel/watcher': 2.5.6
|
'@parcel/watcher': 2.5.6
|
||||||
|
|
||||||
|
saxes@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
xmlchars: 2.2.0
|
||||||
|
|
||||||
scheduler@0.27.0: {}
|
scheduler@0.27.0: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
@ -2498,6 +2788,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.6
|
react: 19.2.6
|
||||||
|
|
||||||
|
symbol-tree@3.2.4: {}
|
||||||
|
|
||||||
three-mesh-bvh@0.8.3(three@0.184.0):
|
three-mesh-bvh@0.8.3(three@0.184.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
three: 0.184.0
|
three: 0.184.0
|
||||||
|
|
@ -2519,6 +2811,20 @@ snapshots:
|
||||||
fdir: 6.5.0(picomatch@4.0.4)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
|
|
||||||
|
tldts-core@6.1.86: {}
|
||||||
|
|
||||||
|
tldts@6.1.86:
|
||||||
|
dependencies:
|
||||||
|
tldts-core: 6.1.86
|
||||||
|
|
||||||
|
tough-cookie@5.1.2:
|
||||||
|
dependencies:
|
||||||
|
tldts: 6.1.86
|
||||||
|
|
||||||
|
tr46@5.1.1:
|
||||||
|
dependencies:
|
||||||
|
punycode: 2.3.1
|
||||||
|
|
||||||
troika-three-text@0.52.4(three@0.184.0):
|
troika-three-text@0.52.4(three@0.184.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
bidi-js: 1.0.3
|
bidi-js: 1.0.3
|
||||||
|
|
@ -2597,16 +2903,39 @@ snapshots:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
sass: 1.100.0
|
sass: 1.100.0
|
||||||
|
|
||||||
|
w3c-xmlserializer@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
xml-name-validator: 5.0.0
|
||||||
|
|
||||||
webgl-constants@1.1.1: {}
|
webgl-constants@1.1.1: {}
|
||||||
|
|
||||||
webgl-sdf-generator@1.1.1: {}
|
webgl-sdf-generator@1.1.1: {}
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
|
whatwg-encoding@3.1.1:
|
||||||
|
dependencies:
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
|
||||||
|
whatwg-mimetype@4.0.0: {}
|
||||||
|
|
||||||
|
whatwg-url@14.2.0:
|
||||||
|
dependencies:
|
||||||
|
tr46: 5.1.1
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
|
ws@8.21.0: {}
|
||||||
|
|
||||||
|
xml-name-validator@5.0.0: {}
|
||||||
|
|
||||||
|
xmlchars@2.2.0: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
|
||||||
35
src/App.tsx
35
src/App.tsx
|
|
@ -2,20 +2,23 @@ import { BrowserRouter, Link, Outlet, Route, Routes, useNavigate, useParams } fr
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { reaction } from 'mobx';
|
import { reaction } from 'mobx';
|
||||||
import { ThreeView } from './components/ThreeView';
|
import { ThreeView } from './components/ThreeView';
|
||||||
import { Panel } from './components/Panel';
|
|
||||||
import { state } from './state';
|
import { state } from './state';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
|
import { LeftPanel } from './components/LeftPanel';
|
||||||
|
import { Panels } from './components/Panels';
|
||||||
|
|
||||||
function StateToUrlSync() {
|
function StateToUrlSync() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useEffect(() => reaction(
|
useEffect(() => reaction(
|
||||||
() => ({
|
() => ({
|
||||||
isGame: state.isGamePlaying,
|
isGame: state.isGamePlaying,
|
||||||
selectedId: state.worldEditor.selectedObjectId,
|
selectedObjectId: state.worldEditor.selection?.type === 'object' && state.worldEditor.selection.id,
|
||||||
|
selectedObjectTypeId: state.worldEditor.selection?.type === 'objectType' && state.worldEditor.selection.id,
|
||||||
}),
|
}),
|
||||||
({ isGame, selectedId }) => {
|
({ isGame, selectedObjectId, selectedObjectTypeId }) => {
|
||||||
if (isGame) navigate('/game');
|
if (isGame) navigate('/game');
|
||||||
else if (selectedId) navigate(`/editor/object/${selectedId}`);
|
else if (selectedObjectId) navigate(`/editor/object/${selectedObjectId}`);
|
||||||
|
else if (selectedObjectTypeId) navigate(`/editor/objectType/${selectedObjectTypeId}`);
|
||||||
else navigate('/editor');
|
else navigate('/editor');
|
||||||
},
|
},
|
||||||
), []);
|
), []);
|
||||||
|
|
@ -25,7 +28,7 @@ function StateToUrlSync() {
|
||||||
function EditorRoute() {
|
function EditorRoute() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.isGamePlaying) state.stopGame();
|
if (state.isGamePlaying) state.stopGame();
|
||||||
state.worldEditor.resetSelectedObject();
|
state.worldEditor.resetSelection();
|
||||||
}, []);
|
}, []);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -35,10 +38,23 @@ function EditorObjectRoute() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
if (state.isGamePlaying) state.stopGame();
|
if (state.isGamePlaying) state.stopGame();
|
||||||
const mode = state.worldEditor.selectedObjectId === id
|
const editMode = state.worldEditor.selection?.type === 'object' && state.worldEditor.selection.id === id
|
||||||
? state.worldEditor.selectedObjectMode ?? 'translate'
|
? state.worldEditor.selection.editMode ?? 'translate'
|
||||||
: 'translate';
|
: 'translate';
|
||||||
state.worldEditor.setSelectedObject(id, mode);
|
state.worldEditor.setSelectedObject({ id, editMode });
|
||||||
|
}, [id]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditorObjectTypeRoute() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return;
|
||||||
|
if (state.isGamePlaying) state.stopGame();
|
||||||
|
const editMode = state.worldEditor.selection?.type === 'objectType' && state.worldEditor.selection.id === id
|
||||||
|
? state.worldEditor.selection.editMode ?? 'scripts'
|
||||||
|
: 'scripts';
|
||||||
|
state.worldEditor.setSelectedObjectType({ id, editMode });
|
||||||
}, [id]);
|
}, [id]);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +75,7 @@ function EditorLayout() {
|
||||||
<>
|
<>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<ThreeView />
|
<ThreeView />
|
||||||
<Panel side="left" />
|
<Panels />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +89,7 @@ export const App = function () {
|
||||||
<Route element={<EditorLayout />}>
|
<Route element={<EditorLayout />}>
|
||||||
<Route path="/editor" element={<EditorRoute />} />
|
<Route path="/editor" element={<EditorRoute />} />
|
||||||
<Route path="/editor/object/:id" element={<EditorObjectRoute />} />
|
<Route path="/editor/object/:id" element={<EditorObjectRoute />} />
|
||||||
|
<Route path="/editor/objectType/:id" element={<EditorObjectTypeRoute />} />
|
||||||
<Route path="/game" element={<GameRoute />} />
|
<Route path="/game" element={<GameRoute />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,19 @@ import { useEffect, useRef } from "react";
|
||||||
import { Euler, Quaternion, Vector3 } from "three";
|
import { Euler, Quaternion, Vector3 } from "three";
|
||||||
import { useFrame, useThree } from "@react-three/fiber";
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { useKeyboardControls } from "@react-three/drei";
|
import { useKeyboardControls } from "@react-three/drei";
|
||||||
import type { RapierRigidBody } from "@react-three/rapier";
|
import { useRapier, useBeforePhysicsStep, type RapierRigidBody, type RapierCollider, RoundCuboidCollider } from "@react-three/rapier";
|
||||||
|
import { joystickValues } from "../joystickInput";
|
||||||
|
|
||||||
const SPEED = 5;
|
const SPEED = 5;
|
||||||
const JUMP_SPEED = 8;
|
const JUMP_SPEED = 8;
|
||||||
|
const GRAVITY = 20;
|
||||||
const SENSITIVITY = 0.002;
|
const SENSITIVITY = 0.002;
|
||||||
// Shoulder offset in character-local space: right, up, behind
|
|
||||||
const SHOULDER_OFFSET = new Vector3(0.1, 1.5, 2.5);
|
const SHOULDER_OFFSET = new Vector3(0.1, 1.5, 2.5);
|
||||||
|
const LOOK_RATE = 2000;
|
||||||
|
|
||||||
|
// recreate private types
|
||||||
|
type RapierWorldCreateCharacterControllerFunction = ReturnType<typeof useRapier>['world']['createCharacterController'];
|
||||||
|
type KinematicCharacterController = ReturnType<RapierWorldCreateCharacterControllerFunction>;
|
||||||
|
|
||||||
const _q = new Quaternion();
|
const _q = new Quaternion();
|
||||||
const _e = new Euler(0, 0, 0, 'YXZ');
|
const _e = new Euler(0, 0, 0, 'YXZ');
|
||||||
|
|
@ -28,19 +34,32 @@ type CharacterViewProps = {
|
||||||
export const CharacterView = observer(function ({ character, editMode }: CharacterViewProps) {
|
export const CharacterView = observer(function ({ character, editMode }: CharacterViewProps) {
|
||||||
const pos = character.transform.position;
|
const pos = character.transform.position;
|
||||||
const rbRef = useRef<RapierRigidBody>(null);
|
const rbRef = useRef<RapierRigidBody>(null);
|
||||||
|
const colliderRef = useRef<RapierCollider>(null);
|
||||||
const [, get] = useKeyboardControls();
|
const [, get] = useKeyboardControls();
|
||||||
const { gl } = useThree();
|
const { gl } = useThree();
|
||||||
const groundContacts = useRef(0);
|
const { world } = useRapier();
|
||||||
|
|
||||||
const yawRef = useRef(0);
|
const yawRef = useRef(0);
|
||||||
const pitchRef = useRef(0);
|
const pitchRef = useRef(0);
|
||||||
const mouseRef = useRef({ x: 0, y: 0 });
|
const mouseRef = useRef({ x: 0, y: 0 });
|
||||||
const jumpPressedRef = useRef(false);
|
const jumpPressedRef = useRef(false);
|
||||||
|
const vyRef = useRef(0);
|
||||||
|
const controllerRef = useRef<KinematicCharacterController | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editMode)
|
const controller = world.createCharacterController(0.01);
|
||||||
return;
|
controller.setUp({ x: 0, y: 1, z: 0 });
|
||||||
|
controller.setMaxSlopeClimbAngle(45 * Math.PI / 180);
|
||||||
|
controller.setMinSlopeSlideAngle(30 * Math.PI / 180);
|
||||||
|
controller.enableAutostep(0.5, 0.2, true);
|
||||||
|
controller.enableSnapToGround(0.5);
|
||||||
|
controller.setApplyImpulsesToDynamicBodies(true);
|
||||||
|
controllerRef.current = controller;
|
||||||
|
return () => { world.removeCharacterController(controller); };
|
||||||
|
}, [world]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editMode) return;
|
||||||
const canvas = gl.domElement;
|
const canvas = gl.domElement;
|
||||||
const onClick = () => canvas.requestPointerLock();
|
const onClick = () => canvas.requestPointerLock();
|
||||||
const onMouseMove = (e: MouseEvent) => {
|
const onMouseMove = (e: MouseEvent) => {
|
||||||
|
|
@ -56,72 +75,81 @@ export const CharacterView = observer(function ({ character, editMode }: Charact
|
||||||
};
|
};
|
||||||
}, [gl, editMode]);
|
}, [gl, editMode]);
|
||||||
|
|
||||||
useFrame(({ camera }) => {
|
useBeforePhysicsStep((world) => {
|
||||||
if (editMode)
|
if (editMode) return;
|
||||||
return;
|
const rb = rbRef.current;
|
||||||
|
const collider = colliderRef.current;
|
||||||
if (!rbRef.current)
|
const controller = controllerRef.current;
|
||||||
return;
|
if (!rb || !collider || !controller) return;
|
||||||
|
if (state.game?.isPaused) return;
|
||||||
yawRef.current -= mouseRef.current.x * SENSITIVITY;
|
|
||||||
pitchRef.current = Math.max(-0.4, Math.min(0.6, pitchRef.current - mouseRef.current.y * SENSITIVITY));
|
|
||||||
mouseRef.current.x = 0;
|
|
||||||
mouseRef.current.y = 0;
|
|
||||||
|
|
||||||
|
const dt = world.timestep;
|
||||||
const yaw = yawRef.current;
|
const yaw = yawRef.current;
|
||||||
|
|
||||||
// Rotate character body on Y axis only
|
|
||||||
_q.setFromEuler(_e.set(0, yaw, 0));
|
|
||||||
rbRef.current.setRotation({ x: _q.x, y: _q.y, z: _q.z, w: _q.w }, true);
|
|
||||||
|
|
||||||
// Pin camera to shoulder offset, rotated by yaw + pitch
|
|
||||||
const t = rbRef.current.translation();
|
|
||||||
_charPos.set(t.x, t.y, t.z);
|
|
||||||
_offset.copy(SHOULDER_OFFSET).applyEuler(_e.set(pitchRef.current, yaw, 0, 'YXZ'));
|
|
||||||
camera.position.copy(_charPos).add(_offset);
|
|
||||||
_lookAt.set(t.x, t.y + 1, t.z);
|
|
||||||
camera.lookAt(_lookAt);
|
|
||||||
|
|
||||||
// Movement relative to character facing direction
|
|
||||||
if (!state.game?.isPaused && !editMode) {
|
|
||||||
const { forward, backward, left, right, jump } = get();
|
const { forward, backward, left, right, jump } = get();
|
||||||
const fwdX = -Math.sin(yaw);
|
const fwdX = -Math.sin(yaw);
|
||||||
const fwdZ = -Math.cos(yaw);
|
const fwdZ = -Math.cos(yaw);
|
||||||
const fwdScale = (forward ? 1 : 0) - (backward ? 1 : 0);
|
const fwdScale = Math.max(-1, Math.min(1, (forward ? 1 : 0) - (backward ? 1 : 0) + joystickValues.move.y));
|
||||||
const rightScale = (right ? 1 : 0) - (left ? 1 : 0);
|
const rightScale = Math.max(-1, Math.min(1, (right ? 1 : 0) - (left ? 1 : 0) + joystickValues.move.x));
|
||||||
// right vector = cross(fwd, up) = (-fwdZ, 0, fwdX)
|
|
||||||
const vx = (fwdX * fwdScale - fwdZ * rightScale) * SPEED;
|
const vx = (fwdX * fwdScale - fwdZ * rightScale) * SPEED;
|
||||||
const vz = (fwdZ * fwdScale + fwdX * rightScale) * SPEED;
|
const vz = (fwdZ * fwdScale + fwdX * rightScale) * SPEED;
|
||||||
const cur = rbRef.current.linvel();
|
|
||||||
|
|
||||||
const isGrounded = groundContacts.current > 0;
|
const jumpInput = jump || joystickValues.jump;
|
||||||
|
const isGrounded = controller.computedGrounded();
|
||||||
const vy = cur.y +
|
if (jumpInput && !jumpPressedRef.current && isGrounded) {
|
||||||
(jump && !jumpPressedRef.current && isGrounded
|
vyRef.current = JUMP_SPEED;
|
||||||
? JUMP_SPEED
|
} else if (!isGrounded) {
|
||||||
: 0);
|
vyRef.current -= GRAVITY * dt;
|
||||||
jumpPressedRef.current = jump;
|
} else {
|
||||||
|
vyRef.current = 0;
|
||||||
rbRef.current.setLinvel({ x: vx, y: vy, z: vz }, true);
|
|
||||||
}
|
}
|
||||||
|
jumpPressedRef.current = jumpInput;
|
||||||
|
|
||||||
|
controller.computeColliderMovement(collider, { x: vx * dt, y: vyRef.current * dt, z: vz * dt });
|
||||||
|
const corrected = controller.computedMovement();
|
||||||
|
const t = rb.translation();
|
||||||
|
rb.setNextKinematicTranslation({
|
||||||
|
x: t.x + corrected.x,
|
||||||
|
y: t.y + corrected.y,
|
||||||
|
z: t.z + corrected.z,
|
||||||
|
});
|
||||||
|
|
||||||
|
_q.setFromEuler(_e.set(0, yaw, 0));
|
||||||
|
rb.setNextKinematicRotation({ x: _q.x, y: _q.y, z: _q.z, w: _q.w });
|
||||||
|
});
|
||||||
|
|
||||||
|
useFrame(({ camera }, delta) => {
|
||||||
|
if (editMode) return;
|
||||||
|
const rb = rbRef.current;
|
||||||
|
if (!rb) return;
|
||||||
|
|
||||||
|
mouseRef.current.x += joystickValues.look.x * LOOK_RATE * delta;
|
||||||
|
mouseRef.current.y += -joystickValues.look.y * LOOK_RATE * delta;
|
||||||
|
|
||||||
|
yawRef.current -= mouseRef.current.x * SENSITIVITY;
|
||||||
|
pitchRef.current = Math.max(-0.85, Math.min(0.5, pitchRef.current - mouseRef.current.y * SENSITIVITY));
|
||||||
|
mouseRef.current.x = 0;
|
||||||
|
mouseRef.current.y = 0;
|
||||||
|
|
||||||
|
const t = rb.translation();
|
||||||
|
_charPos.set(t.x, t.y, t.z);
|
||||||
|
_offset.copy(SHOULDER_OFFSET).applyEuler(_e.set(pitchRef.current, yawRef.current, 0, 'YXZ'));
|
||||||
|
camera.position.copy(_charPos).add(_offset);
|
||||||
|
_lookAt.set(t.x, t.y + 1, t.z);
|
||||||
|
camera.lookAt(_lookAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SyncRigidBody
|
<SyncRigidBody
|
||||||
ref={rbRef}
|
ref={rbRef}
|
||||||
colliders="cuboid"
|
type="kinematicPosition"
|
||||||
lockRotations
|
colliders={false}
|
||||||
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
|
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
|
||||||
onCollisionEnter={() => { groundContacts.current++; }}
|
onSync={() => { }}
|
||||||
onCollisionExit={() => { groundContacts.current--; }}
|
|
||||||
onSync={(data) => {
|
|
||||||
// state.game?.setCharacterTransform(
|
|
||||||
// { position: data.position, look: character.transform.look },
|
|
||||||
// data.linearVelocity,
|
|
||||||
// undefined,
|
|
||||||
// );
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
{/* <BallCollider ref={colliderRef} args={[0.55]} /> */}
|
||||||
|
{/* <CapsuleCollider ref={colliderRef} args={[0.4, 0.5]} /> */}
|
||||||
|
{/* <CuboidCollider ref={colliderRef} args={[0.55, 0.4, 0.55]} /> */}
|
||||||
|
<RoundCuboidCollider ref={colliderRef} args={[0.4, 0.4, 0.4, 0.1]} />
|
||||||
<group>
|
<group>
|
||||||
<mesh rotation={[-Math.PI / 2, -Math.PI / 4, 0]}>
|
<mesh rotation={[-Math.PI / 2, -Math.PI / 4, 0]}>
|
||||||
<coneGeometry args={[0.55, 0.8, 4]} />
|
<coneGeometry args={[0.55, 0.8, 4]} />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { create } from "nipplejs";
|
||||||
|
import { joystickValues } from "../joystickInput";
|
||||||
|
|
||||||
|
// const isTouch = navigator.maxTouchPoints > 0;
|
||||||
|
const isTouch = true; // debug
|
||||||
|
|
||||||
|
export function JoystickView() {
|
||||||
|
const moveZoneRef = useRef<HTMLDivElement>(null);
|
||||||
|
const jumpBtnRef = useRef<HTMLDivElement>(null);
|
||||||
|
const lookZoneRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!isTouch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const moveZone = moveZoneRef.current;
|
||||||
|
const jumpBtn = jumpBtnRef.current;
|
||||||
|
const lookZone = lookZoneRef.current;
|
||||||
|
|
||||||
|
if (!moveZone || !jumpBtn || !lookZone)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let moveJoystick: ReturnType<typeof create>;
|
||||||
|
let lookJoystick: ReturnType<typeof create>;
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
moveJoystick = create({
|
||||||
|
zone: moveZone,
|
||||||
|
mode: 'static',
|
||||||
|
position: { left: '80px', bottom: '80px' },
|
||||||
|
color: 'white',
|
||||||
|
shape: 'circle',
|
||||||
|
size: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
lookJoystick = create({
|
||||||
|
zone: lookZone,
|
||||||
|
mode: 'dynamic',
|
||||||
|
dataOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
moveJoystick.on('move', (evt) => {
|
||||||
|
joystickValues.move.x = evt.data.vector.x;
|
||||||
|
joystickValues.move.y = evt.data.vector.y;
|
||||||
|
});
|
||||||
|
moveJoystick.on('end', () => {
|
||||||
|
joystickValues.move = { x: 0, y: 0 };
|
||||||
|
});
|
||||||
|
|
||||||
|
lookJoystick.on('move', (evt) => {
|
||||||
|
joystickValues.look = evt.data.vector;
|
||||||
|
});
|
||||||
|
lookJoystick.on('end', () => {
|
||||||
|
joystickValues.look = { x: 0, y: 0 };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const teardown = () => {
|
||||||
|
moveJoystick?.destroy();
|
||||||
|
lookJoystick?.destroy();
|
||||||
|
joystickValues.move = { x: 0, y: 0 };
|
||||||
|
joystickValues.look = { x: 0, y: 0 };
|
||||||
|
joystickValues.jump = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
const onJumpDown = () => { joystickValues.jump = true; };
|
||||||
|
const onJumpUp = () => { joystickValues.jump = false; };
|
||||||
|
|
||||||
|
jumpBtn.addEventListener('pointerdown', onJumpDown);
|
||||||
|
jumpBtn.addEventListener('pointerup', onJumpUp);
|
||||||
|
jumpBtn.addEventListener('pointercancel', onJumpUp);
|
||||||
|
jumpBtn.addEventListener('pointerleave', onJumpUp);
|
||||||
|
|
||||||
|
// When the OS interrupts a touch (app switch, notification, etc.) the browser
|
||||||
|
// fires pointercancel instead of pointerup, leaving nipplejs with a stuck active
|
||||||
|
// identifier. In dynamic mode this silently drops all subsequent touches on the
|
||||||
|
// look zone. Destroying and recreating resets nipplejs's internal touch tracking.
|
||||||
|
const onVisibilityChange = () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
teardown();
|
||||||
|
} else {
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
teardown();
|
||||||
|
jumpBtn.removeEventListener('pointerdown', onJumpDown);
|
||||||
|
jumpBtn.removeEventListener('pointerup', onJumpUp);
|
||||||
|
jumpBtn.removeEventListener('pointercancel', onJumpUp);
|
||||||
|
jumpBtn.removeEventListener('pointerleave', onJumpUp);
|
||||||
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!isTouch)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
ref={moveZoneRef}
|
||||||
|
id="move-zone"
|
||||||
|
style={{ position: 'absolute', left: 0, bottom: 0, width: '30vw', height: '30vw', touchAction: 'none' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
ref={lookZoneRef}
|
||||||
|
id="look-zone"
|
||||||
|
style={{ position: 'absolute', right: 0, bottom: 0, width: '100%', height: '100%', touchAction: 'none' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
ref={jumpBtnRef}
|
||||||
|
id="jump-zone"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 30,
|
||||||
|
bottom: 30,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(255,255,255,0.25)',
|
||||||
|
border: '2px solid rgba(255,255,255,0.6)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
touchAction: 'none',
|
||||||
|
userSelect: 'none',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
JUMP
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { RenderInfoView } from "./RenderInfoView";
|
||||||
|
import { MenuView } from "./MenuView";
|
||||||
|
import { state } from "../state";
|
||||||
|
import { Panel } from "./Panel";
|
||||||
|
|
||||||
|
export const LeftPanel = observer(function () {
|
||||||
|
const isGame = state.game && !state.game.isPaused;
|
||||||
|
|
||||||
|
if (isGame)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// function handleLoadWorld(): void {
|
||||||
|
// state.world.load();
|
||||||
|
// }
|
||||||
|
|
||||||
|
function handleLoadMockWorld(): void {
|
||||||
|
state.world.loadMock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Panel side='left'>
|
||||||
|
<MenuView />
|
||||||
|
<div className="gap" />
|
||||||
|
{/* <button onClick={handleLoadWorld}>Load</button> */}
|
||||||
|
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
||||||
|
<div className="debug"><RenderInfoView /></div>
|
||||||
|
</Panel>
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { state } from "../state";
|
||||||
|
import { Panel } from "./Panel";
|
||||||
|
import { ScriptEditorView } from "./scriptEditor/ScriptEditorView";
|
||||||
|
|
||||||
|
export const MainPanel = observer(function () {
|
||||||
|
const isGame = state.game && !state.game.isPaused;
|
||||||
|
if (isGame)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const selectedObjectTypeId = state.worldEditor.selection?.type === 'objectType'
|
||||||
|
? state.worldEditor.selection.id
|
||||||
|
: undefined;
|
||||||
|
if (!selectedObjectTypeId)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const objectType = state.world.getObjectTypeById(selectedObjectTypeId);
|
||||||
|
if (!objectType)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const objects = Object.values(state.worldEditor.scene.objects)
|
||||||
|
.filter((o) => o.typeId === objectType.id);
|
||||||
|
|
||||||
|
return <Panel side='main'>
|
||||||
|
<header>
|
||||||
|
<div><span className="title">{objectType?.name}</span> ({objects.length} object{objects.length > 1 ? 's' : ''})</div>
|
||||||
|
</header>
|
||||||
|
<ScriptEditorView objectType={objectType} />
|
||||||
|
</Panel>
|
||||||
|
});
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
.menu {
|
.menu {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
|
||||||
|
|
||||||
.menu-node {
|
& details {
|
||||||
> summary {
|
&>summary {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
&::-webkit-details-marker { display: none; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item {
|
&::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
padding: 8px 8px;
|
padding: 8px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -18,23 +17,21 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 1);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.selected {
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-node .menu-node > summary {
|
& details {
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.menu-node .menu-node .menu-leaf {
|
|
||||||
padding-left: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,33 +1,36 @@
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
import { state } from '../state';
|
import { state } from '../state';
|
||||||
import './MenuView.scss';
|
import './MenuView.scss';
|
||||||
|
import type { MenuNode } from '../state/menuState';
|
||||||
|
|
||||||
export const MenuView = observer(function () {
|
export const MenuNodeView = observer(function ({ node }: { node: MenuNode }) {
|
||||||
const objectIds = Object.keys(state.worldEditor.scene.objects);
|
const forceOpen = state.menu.nodeContainsSelected(node);
|
||||||
const selectedId = state.worldEditor.selectedObjectId;
|
|
||||||
|
const ref = useRef<HTMLDetailsElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (forceOpen && ref.current)
|
||||||
|
ref.current.open = true;
|
||||||
|
}, [forceOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="menu">
|
<details ref={ref}>
|
||||||
<details open className="menu-node">
|
|
||||||
<summary className="menu-item">Editor</summary>
|
|
||||||
<details open className="menu-node">
|
|
||||||
<summary
|
<summary
|
||||||
className={`menu-item${selectedId == null ? ' active' : ''}`}
|
className={node.selected?.() ? 'selected' : ''}
|
||||||
onClick={() => state.worldEditor.resetSelectedObject()}
|
onClick={() => node.onClick?.()}
|
||||||
>
|
>
|
||||||
Objects
|
{node.title}
|
||||||
</summary>
|
</summary>
|
||||||
{objectIds.map(id => (
|
{node.children?.map((child) => <MenuNodeView key={child.id} node={child} />)}
|
||||||
<div
|
|
||||||
key={id}
|
|
||||||
className={`menu-item menu-leaf${selectedId === id ? ' active' : ''}`}
|
|
||||||
onClick={() => state.worldEditor.setSelectedObject(id, 'translate')}
|
|
||||||
>
|
|
||||||
{id}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</details>
|
|
||||||
</details>
|
</details>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MenuView = observer(function () {
|
||||||
|
return (
|
||||||
|
<nav className="menu">
|
||||||
|
{state.menu.nodes.map((node) => <MenuNodeView key={node.id} node={node} />)}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { TransformControls, useHelper } from "@react-three/drei";
|
||||||
import { BoxHelper } from "three";
|
import { BoxHelper } from "three";
|
||||||
import type { ThreeEvent } from "@react-three/fiber";
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
import { nextSelectionEditMode } from "../state/worldEditorState";
|
import { nextObjectEditMode } from "../state/worldEditorState";
|
||||||
import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal";
|
import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal";
|
||||||
|
|
||||||
type ObjectEditorViewProps = {
|
type ObjectEditorViewProps = {
|
||||||
|
|
@ -23,8 +23,12 @@ type SelectionOverlayProps = {
|
||||||
// re-render on selection change, not all N objects in the scene.
|
// re-render on selection change, not all N objects in the scene.
|
||||||
const SelectionOverlay = observer(function ({ objectId, ref, onTransformEnd }: SelectionOverlayProps) {
|
const SelectionOverlay = observer(function ({ objectId, ref, onTransformEnd }: SelectionOverlayProps) {
|
||||||
const isSelected = state.worldEditor.isEnabled &&
|
const isSelected = state.worldEditor.isEnabled &&
|
||||||
state.worldEditor.selectedObjectId === objectId;
|
state.worldEditor.selection?.type === 'object' &&
|
||||||
const selectionMode = isSelected ? state.worldEditor.selectedObjectMode : undefined;
|
state.worldEditor.selection.id === objectId;
|
||||||
|
const selectionMode = isSelected &&
|
||||||
|
state.worldEditor.selection?.type === 'object'
|
||||||
|
? state.worldEditor.selection?.editMode
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Stable virtual ref that reads through to the group inside the handle
|
// Stable virtual ref that reads through to the group inside the handle
|
||||||
const groupRef = useMemo<RefObject<Group | null>>(
|
const groupRef = useMemo<RefObject<Group | null>>(
|
||||||
|
|
@ -60,10 +64,10 @@ export const ObjectEditorView = observer(function ({ object }: ObjectEditorViewP
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// Reading selection state inside an event handler: not tracked by observer.
|
// Reading selection state inside an event handler: not tracked by observer.
|
||||||
const currentMode = state.worldEditor.isEnabled &&
|
const currentMode = state.worldEditor.isEnabled &&
|
||||||
state.worldEditor.selectedObjectId === object.id
|
state.worldEditor.selection?.type === 'object' && state.worldEditor.selection?.id === object.id
|
||||||
? state.worldEditor.selectedObjectMode
|
? state.worldEditor.selection?.editMode
|
||||||
: undefined;
|
: undefined;
|
||||||
state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(currentMode));
|
state.worldEditor.setSelectedObject({ id: object.id, editMode: nextObjectEditMode(currentMode) });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTransformEnd() {
|
function handleTransformEnd() {
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
.panel {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
width: 30%;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 10px;
|
|
||||||
z-index: 10;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&.left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.container {
|
|
||||||
flex: 1;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25em;
|
|
||||||
|
|
||||||
&>*:not(.debug):not(.gap) {
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.gap {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.debug {
|
|
||||||
border: 1px solid #ffffff20;
|
|
||||||
padding: 2px;
|
|
||||||
font-size: 75%;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
&>.chart {
|
|
||||||
display: inline;
|
|
||||||
|
|
||||||
& div {
|
|
||||||
display: inline;
|
|
||||||
position: relative !important;
|
|
||||||
z-index: unset !important;
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.metrics {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25em 0.5em;
|
|
||||||
|
|
||||||
&>* {
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +1,13 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import './Panel.scss';
|
import type { ReactNode } from "react";
|
||||||
import { RenderInfoView } from "./RenderInfoView";
|
|
||||||
import { MenuView } from "./MenuView";
|
|
||||||
import { state } from "../state";
|
|
||||||
|
|
||||||
export type PanelProps = {
|
export type PanelProps = {
|
||||||
side?: 'left' | 'right';
|
side?: 'left' | 'right' | 'main';
|
||||||
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Panel = observer(function ({ side = 'left' }: PanelProps) {
|
export const Panel = observer(function ({ children, side = 'left' }: PanelProps) {
|
||||||
|
|
||||||
function handleCloneTest1Object(): void {
|
|
||||||
state.worldEditor.addObjectCloneAtRandomPosition('test1');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoadWorld(): void {
|
|
||||||
state.world.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoadMockWorld(): void {
|
|
||||||
state.world.loadMock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className={`panel ${side}`}>
|
return <div className={`panel ${side}`}>
|
||||||
<div className="container">
|
{children}
|
||||||
<MenuView />
|
|
||||||
<button onClick={handleLoadWorld}>Load</button>
|
|
||||||
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
|
||||||
<button onClick={handleCloneTest1Object}>Clone test1</button>
|
|
||||||
<div className="gap" />
|
|
||||||
<div className="debug"><RenderInfoView /></div>
|
|
||||||
</div>
|
|
||||||
</div >
|
</div >
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
.overlay-panels {
|
||||||
|
z-index: 15000;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&>.panel {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left,
|
||||||
|
&.right {
|
||||||
|
min-width: 30%;
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.main {
|
||||||
|
flex: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>header {
|
||||||
|
background: #00000040;
|
||||||
|
margin: -4px;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
&>.title {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&>*:not(.debug):not(.header):not(.gap) {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.gap {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.debug {
|
||||||
|
// border: 1px solid #ffffff20;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #00000040;
|
||||||
|
|
||||||
|
margin: -4px;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 75%;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
&>.chart {
|
||||||
|
display: inline;
|
||||||
|
|
||||||
|
& div {
|
||||||
|
display: inline;
|
||||||
|
position: relative !important;
|
||||||
|
z-index: unset !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.metrics {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25em 0.5em;
|
||||||
|
|
||||||
|
&>* {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { LeftPanel } from "./LeftPanel";
|
||||||
|
import { MainPanel } from "./MainPanel";
|
||||||
|
import './Panels.scss';
|
||||||
|
|
||||||
|
export const Panels = observer(function () {
|
||||||
|
|
||||||
|
return <div className="overlay-panels">
|
||||||
|
<LeftPanel />
|
||||||
|
<MainPanel />
|
||||||
|
</div>
|
||||||
|
});
|
||||||
|
|
@ -24,6 +24,7 @@ import { observer } from 'mobx-react-lite';
|
||||||
import { state } from '../state';
|
import { state } from '../state';
|
||||||
import { GameView } from './GameView';
|
import { GameView } from './GameView';
|
||||||
import { SceneEditorView } from './SceneEditorView';
|
import { SceneEditorView } from './SceneEditorView';
|
||||||
|
import { JoystickView } from './JoystickView';
|
||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
|
|
||||||
const IconStop = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="10" height="10" fill="currentColor" /></svg>;
|
const IconStop = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="10" height="10" fill="currentColor" /></svg>;
|
||||||
|
|
@ -43,12 +44,13 @@ export const ThreeView = observer(function () {
|
||||||
<div style={{ position: 'fixed', inset: 0, overflow: 'hidden' }}>
|
<div style={{ position: 'fixed', inset: 0, overflow: 'hidden' }}>
|
||||||
<Canvas
|
<Canvas
|
||||||
// camera={state.world.character.camera}
|
// camera={state.world.character.camera}
|
||||||
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
onPointerMissed={() => state.worldEditor.resetSelection()}
|
||||||
>
|
>
|
||||||
<Stats parent={chartRef as RefObject<HTMLElement>} />
|
<Stats parent={chartRef as RefObject<HTMLElement>} />
|
||||||
<RenderInfoUpdater />
|
<RenderInfoUpdater />
|
||||||
{isGame ? <GameView /> : <SceneEditorView />}
|
{isGame ? <GameView /> : <SceneEditorView />}
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
{isGame && <JoystickView />}
|
||||||
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
||||||
{
|
{
|
||||||
state.game
|
state.game
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.script-editor {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.blockly-container {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import * as Blockly from "blockly";
|
||||||
|
import type { ObjectType } from "../../types";
|
||||||
|
import './ScriptEditorView.scss';
|
||||||
|
|
||||||
|
const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
|
||||||
|
kind: 'flyoutToolbox',
|
||||||
|
contents: [
|
||||||
|
{ kind: 'block', type: 'controls_if' },
|
||||||
|
{ kind: 'block', type: 'controls_repeat_ext' },
|
||||||
|
{ kind: 'block', type: 'logic_compare' },
|
||||||
|
{ kind: 'block', type: 'math_number' },
|
||||||
|
{ kind: 'block', type: 'math_arithmetic' },
|
||||||
|
{ kind: 'block', type: 'text' },
|
||||||
|
{ kind: 'block', type: 'text_print' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
type ScriptEditorViewProps = {
|
||||||
|
objectType: ObjectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorViewProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const workspaceRef = useRef<Blockly.WorkspaceSvg | null>(null);
|
||||||
|
const isLoadingRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
const workspace = Blockly.inject(containerRef.current, { toolbox: TOOLBOX });
|
||||||
|
workspaceRef.current = workspace;
|
||||||
|
|
||||||
|
if (objectType.program) {
|
||||||
|
try {
|
||||||
|
isLoadingRef.current = true;
|
||||||
|
Blockly.serialization.workspaces.load(JSON.parse(objectType.program), workspace);
|
||||||
|
} catch {
|
||||||
|
// corrupt program, start fresh
|
||||||
|
} finally {
|
||||||
|
isLoadingRef.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = (e: Blockly.Events.Abstract) => {
|
||||||
|
if (isLoadingRef.current || e.isUiEvent) return;
|
||||||
|
const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
|
||||||
|
runInAction(() => { objectType.program = serialized; });
|
||||||
|
};
|
||||||
|
workspace.addChangeListener(listener);
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => Blockly.svgResize(workspace));
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
workspace.removeChangeListener(listener);
|
||||||
|
workspace.dispose();
|
||||||
|
workspaceRef.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const workspace = workspaceRef.current;
|
||||||
|
if (!workspace) return;
|
||||||
|
|
||||||
|
isLoadingRef.current = true;
|
||||||
|
workspace.clear();
|
||||||
|
if (objectType.program) {
|
||||||
|
try {
|
||||||
|
Blockly.serialization.workspaces.load(JSON.parse(objectType.program), workspace);
|
||||||
|
} catch {
|
||||||
|
// corrupt program, start fresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isLoadingRef.current = false;
|
||||||
|
}, [objectType.id]);
|
||||||
|
|
||||||
|
return <div className="script-editor">
|
||||||
|
<div ref={containerRef} className="blockly-container" />
|
||||||
|
</div>;
|
||||||
|
});
|
||||||
|
|
@ -64,13 +64,13 @@ body {
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
// *:not(.panel.main) svg {
|
||||||
fill: currentColor;
|
// fill: currentColor;
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
height: 1em;
|
// height: 1em;
|
||||||
top: 0.125em;
|
// top: 0.125em;
|
||||||
vertical-align: bottom;
|
// vertical-align: bottom;
|
||||||
}
|
// }
|
||||||
|
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
|
|
@ -78,3 +78,7 @@ select,
|
||||||
textarea {
|
textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const DEFAULT_JOYSTICK_VALUES = {
|
||||||
|
move: { x: 0, y: 0 },
|
||||||
|
jump: false,
|
||||||
|
look: { x: 0, y: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const joystickValues = DEFAULT_JOYSTICK_VALUES;
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { makeAutoObservable } from "mobx";
|
||||||
|
import { state } from "./rootState";
|
||||||
|
|
||||||
|
export type MenuNode = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
selected?: () => boolean;
|
||||||
|
children?: MenuNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MenuState {
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get editorObjectTypesMenu(): MenuNode[] {
|
||||||
|
return Object.values(state.world.data.objectTypes)
|
||||||
|
.map((ot) => ({
|
||||||
|
id: `ot-${ot.id}`,
|
||||||
|
title: ot.name,
|
||||||
|
onClick: () => { state.worldEditor.setSelectedObjectType({ id: ot.id, editMode: 'scripts' }) },
|
||||||
|
selected: () => state.worldEditor.selection?.type === 'objectType' && state.worldEditor.selection?.id === ot.id,
|
||||||
|
children: Object.values(state.worldEditor.scene.objects)
|
||||||
|
.filter((o) => o.typeId === ot.id)
|
||||||
|
.map((o) => ({
|
||||||
|
id: `o-${o.id}`,
|
||||||
|
title: o.id,
|
||||||
|
onClick: () => { state.worldEditor.setSelectedObject({ id: o.id, editMode: 'translate' }) },
|
||||||
|
selected: () => state.worldEditor.selection?.type === 'object' && state.worldEditor.selection?.id === o.id,
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private get editorMenu(): MenuNode[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'editor-objects-menu',
|
||||||
|
title: 'Objects',
|
||||||
|
onClick: () => { state.worldEditor.resetSelection() },
|
||||||
|
selected: () => !state.worldEditor.selection,
|
||||||
|
children: this.editorObjectTypesMenu,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodes(): MenuNode[] {
|
||||||
|
return [
|
||||||
|
...this.editorMenu,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
public nodeContainsSelected(node: MenuNode): boolean {
|
||||||
|
return !!(node.selected?.() || node.children?.some((child) => this.nodeContainsSelected(child)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { makeAutoObservable } from "mobx";
|
||||||
import { WorldState } from "./worldState";
|
import { WorldState } from "./worldState";
|
||||||
import { WorldEditorState } from "./worldEditorState";
|
import { WorldEditorState } from "./worldEditorState";
|
||||||
import { GameState } from "./gameState";
|
import { GameState } from "./gameState";
|
||||||
|
import { MenuState } from "./menuState";
|
||||||
|
|
||||||
export type RenderInfo = {
|
export type RenderInfo = {
|
||||||
calls: number,
|
calls: number,
|
||||||
|
|
@ -16,6 +17,7 @@ export class RootState {
|
||||||
public readonly worldEditor: WorldEditorState;
|
public readonly worldEditor: WorldEditorState;
|
||||||
public game: GameState | undefined;
|
public game: GameState | undefined;
|
||||||
public renderInfo: RenderInfo | undefined;
|
public renderInfo: RenderInfo | undefined;
|
||||||
|
public readonly menu = new MenuState();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.worldEditor = new WorldEditorState(this.world);
|
this.worldEditor = new WorldEditorState(this.world);
|
||||||
|
|
@ -24,6 +26,7 @@ export class RootState {
|
||||||
this,
|
this,
|
||||||
{
|
{
|
||||||
world: false,
|
world: false,
|
||||||
|
menu: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,54 @@ import { randomId } from "../utils";
|
||||||
import { state } from "./rootState";
|
import { state } from "./rootState";
|
||||||
import { populateRuntimeObject } from "../utils/runtime";
|
import { populateRuntimeObject } from "../utils/runtime";
|
||||||
|
|
||||||
export const SelectionEditModeEnum = [
|
export const ObjectEditModeEnum = [
|
||||||
'translate',
|
'translate',
|
||||||
'rotate',
|
'rotate',
|
||||||
'scale',
|
'scale',
|
||||||
] as const;
|
] as const;
|
||||||
type SelectionEditModeTuple = typeof SelectionEditModeEnum;
|
type ObjectEditModeTuple = typeof ObjectEditModeEnum;
|
||||||
export type SelectionEditMode = SelectionEditModeTuple[number];
|
export type ObjectEditMode = ObjectEditModeTuple[number];
|
||||||
|
|
||||||
export function nextSelectionEditMode(mode: SelectionEditMode | undefined): SelectionEditMode {
|
export function nextObjectEditMode(mode: ObjectEditMode | undefined): ObjectEditMode {
|
||||||
if (mode === undefined)
|
if (mode === undefined)
|
||||||
return 'translate';
|
return ObjectEditModeEnum[0];
|
||||||
|
|
||||||
const idx = SelectionEditModeEnum.indexOf(mode);
|
const idx = ObjectEditModeEnum.indexOf(mode);
|
||||||
return SelectionEditModeEnum[(idx + 1) % SelectionEditModeEnum.length];
|
return ObjectEditModeEnum[(idx + 1) % ObjectEditModeEnum.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ObjectTypeEditModeEnum = [
|
||||||
|
'scripts',
|
||||||
|
] as const;
|
||||||
|
type ObjectTypeEditModeTuple = typeof ObjectTypeEditModeEnum;
|
||||||
|
export type ObjectTypeEditMode = ObjectTypeEditModeTuple[number];
|
||||||
|
|
||||||
|
export function nextObjectTypeEditMode(mode: ObjectTypeEditMode | undefined): ObjectTypeEditMode {
|
||||||
|
if (mode === undefined)
|
||||||
|
return ObjectTypeEditModeEnum[0];
|
||||||
|
|
||||||
|
const idx = ObjectTypeEditModeEnum.indexOf(mode);
|
||||||
|
return ObjectTypeEditModeEnum[(idx + 1) % ObjectTypeEditModeEnum.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectedObject = {
|
||||||
|
type: 'object',
|
||||||
|
id: string;
|
||||||
|
editMode: ObjectEditMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectedObjectType = {
|
||||||
|
type: 'objectType',
|
||||||
|
id: string;
|
||||||
|
editMode: ObjectTypeEditMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Selection = SelectedObject | SelectedObjectType;
|
||||||
|
|
||||||
export class WorldEditorState {
|
export class WorldEditorState {
|
||||||
private readonly world: WorldState;
|
private readonly world: WorldState;
|
||||||
|
|
||||||
public selectedObjectId: string | undefined;
|
public selection: Selection | undefined;
|
||||||
public selectedObjectMode: SelectionEditMode | undefined;
|
|
||||||
|
|
||||||
constructor(world: WorldState) {
|
constructor(world: WorldState) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
|
|
@ -41,14 +68,33 @@ export class WorldEditorState {
|
||||||
this.world.data.editorCamera = value;
|
this.world.data.editorCamera = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSelectedObject(id: string, mode: SelectionEditMode): void {
|
public setSelection(value: Selection | undefined): void {
|
||||||
this.selectedObjectId = id;
|
this.selection = value;
|
||||||
this.selectedObjectMode = mode;
|
console.log(JSON.stringify(this.selection));
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetSelectedObject(): void {
|
public setSelectedObject(value: Omit<SelectedObject, 'type'> | undefined): void {
|
||||||
this.selectedObjectId = undefined;
|
this.setSelection(value
|
||||||
this.selectedObjectMode = undefined;
|
? {
|
||||||
|
type: 'object',
|
||||||
|
...value,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSelectedObjectType(value: Omit<SelectedObjectType, 'type'> | undefined): void {
|
||||||
|
this.setSelection(value
|
||||||
|
? {
|
||||||
|
type: 'objectType',
|
||||||
|
...value,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetSelection() {
|
||||||
|
this.setSelection(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get scene(): RuntimeScene {
|
public get scene(): RuntimeScene {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class WorldState {
|
||||||
this.withoutAutoSave(() => {
|
this.withoutAutoSave(() => {
|
||||||
this.data = WorldFactory.create();
|
this.data = WorldFactory.create();
|
||||||
});
|
});
|
||||||
state.worldEditor.resetSelectedObject();
|
state.worldEditor.resetSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadMock() {
|
public loadMock() {
|
||||||
|
|
@ -97,7 +97,7 @@ export class WorldState {
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(objects);
|
console.log(objects);
|
||||||
state.worldEditor.resetSelectedObject();
|
state.worldEditor.resetSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public load() {
|
public load() {
|
||||||
|
|
@ -105,7 +105,7 @@ export class WorldState {
|
||||||
this.withoutAutoSave(() => {
|
this.withoutAutoSave(() => {
|
||||||
this.data = WorldFactory.load() ?? WorldFactory.create();
|
this.data = WorldFactory.load() ?? WorldFactory.create();
|
||||||
});
|
});
|
||||||
state.worldEditor.resetSelectedObject();
|
state.worldEditor.resetSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveData(data: World): void {
|
private saveData(data: World): void {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export type ObjectType = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
voxels: Voxel[];
|
voxels: Voxel[];
|
||||||
|
program?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ObjectInstance = {
|
export type ObjectInstance = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue