diff --git a/.vscode/settings.json b/.vscode/settings.json index 75adeac..4de5807 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,6 +59,7 @@ "iface", "importas", "INGAME", + "interactables", "Ints", "ireturn", "irongut", diff --git a/TODO b/TODO index e3506d8..24e1817 100644 --- a/TODO +++ b/TODO @@ -12,6 +12,8 @@ Line of action Lint ☐ Fix all linter issues ☐ Disable unused lints (remove if default satisfies) + ☐ Update README for frontend and move frontend to another repo + ☐ Make taskfile to shim frontend for go to run tests without building using Wails. Remove the step from CI pipeline Refactors ☐ Migrate to data-driven approach ☐ Benchmark performance for room and connection against direct data storage instead of using pointers diff --git a/pkg/client/frontend/package-lock.json b/pkg/client/frontend/package-lock.json index 6649c5e..4b60b0c 100644 --- a/pkg/client/frontend/package-lock.json +++ b/pkg/client/frontend/package-lock.json @@ -13,15 +13,17 @@ "@tweenjs/tween.js": "^25.0.0", "solid-js": "^1.9.3", "solid-spinner": "^0.2.0", + "solid-three": "^0.2.0", "solid-toast": "^0.5.0", "tailwind-merge": "^2.6.0", - "three": "^0.172.0" + "three": "^0.175.0", + "three-stdlib": "^2.35.15" }, "devDependencies": { "@pyncz/tailwind-mask-image": "^2.0.0", "@trivago/prettier-plugin-sort-imports": "^5.2.1", "@types/node": "^22.10.5", - "@types/three": "^0.172.0", + "@types/three": "^0.175.0", "@xpd/tailwind-3dtransforms": "^1.0.3", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", @@ -36,6 +38,9 @@ "vite-plugin-solid": "^2.11.0" } }, + "../../../../../three-tour-controls": { + "extraneous": true + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -236,27 +241,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", - "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.5" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -282,15 +287,15 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -316,9 +321,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", - "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "license": "MIT", "dependencies": { @@ -330,9 +335,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "cpu": [ "ppc64" ], @@ -347,9 +352,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], @@ -364,9 +369,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], @@ -381,9 +386,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], @@ -398,9 +403,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], @@ -415,9 +420,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], @@ -432,9 +437,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], @@ -449,9 +454,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], @@ -466,9 +471,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], @@ -483,9 +488,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], @@ -500,9 +505,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], @@ -517,9 +522,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], @@ -534,9 +539,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], @@ -551,9 +556,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], @@ -568,9 +573,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], @@ -585,9 +590,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], @@ -602,9 +607,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], @@ -619,9 +624,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "cpu": [ "arm64" ], @@ -636,9 +641,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], @@ -653,9 +658,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "cpu": [ "arm64" ], @@ -670,9 +675,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], @@ -687,9 +692,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], @@ -704,9 +709,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], @@ -721,9 +726,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], @@ -738,9 +743,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], @@ -1012,9 +1017,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", - "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "cpu": [ "arm" ], @@ -1026,9 +1031,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz", - "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "cpu": [ "arm64" ], @@ -1040,9 +1045,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz", - "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "cpu": [ "arm64" ], @@ -1054,9 +1059,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz", - "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "cpu": [ "x64" ], @@ -1068,9 +1073,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz", - "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", "cpu": [ "arm64" ], @@ -1082,9 +1087,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz", - "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", "cpu": [ "x64" ], @@ -1096,9 +1101,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz", - "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "cpu": [ "arm" ], @@ -1110,9 +1115,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz", - "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "cpu": [ "arm" ], @@ -1124,9 +1129,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz", - "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "cpu": [ "arm64" ], @@ -1138,9 +1143,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz", - "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "cpu": [ "arm64" ], @@ -1152,9 +1157,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz", - "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", "cpu": [ "loong64" ], @@ -1166,9 +1171,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz", - "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "cpu": [ "ppc64" ], @@ -1180,9 +1185,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz", - "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", "cpu": [ "riscv64" ], @@ -1194,9 +1213,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz", - "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "cpu": [ "s390x" ], @@ -1208,9 +1227,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz", - "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "cpu": [ "x64" ], @@ -1222,9 +1241,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz", - "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "cpu": [ "x64" ], @@ -1236,9 +1255,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz", - "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "cpu": [ "arm64" ], @@ -1250,9 +1269,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz", - "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "cpu": [ "ia32" ], @@ -1264,9 +1283,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz", - "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "cpu": [ "x64" ], @@ -1385,10 +1404,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", + "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -1402,6 +1427,12 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, "node_modules/@types/stats.js": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", @@ -1410,9 +1441,9 @@ "license": "MIT" }, "node_modules/@types/three": { - "version": "0.172.0", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.172.0.tgz", - "integrity": "sha512-LrUtP3FEG26Zg5WiF0nbg8VoXiKokBLTcqM2iLvM9vzcfEiYmmBAPGdBgV0OYx9fvWlY3R/3ERTZcD9X5sc0NA==", + "version": "0.175.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.175.0.tgz", + "integrity": "sha512-ldMSBgtZOZ3g9kJ3kOZSEtZIEITmJOzu8eKVpkhf036GuNkM4mt0NXecrjCn5tMm1OblOF7dZehlaDypBfNokw==", "dev": true, "license": "MIT", "dependencies": { @@ -1431,17 +1462,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/three/node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/webxr": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.21.tgz", "integrity": "sha512-geZIAtLzjGmgY2JUi6VxXdCrTb99A7yP49lxLr2Nm/uIK0PkkxcEi4OGhoGDO4pxCf3JwGz2GiJL2Ej4K2bKaA==", - "dev": true, "license": "MIT" }, "node_modules/@webgpu/types": { - "version": "0.1.53", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.53.tgz", - "integrity": "sha512-x+BLw/opaz9LiVyrMsP75nO1Rg0QfrACUYIbVSfGwY/w0DiWIPYYrpte6us//KZXinxFAOJl0+C17L1Vi2vmDw==", + "version": "0.1.60", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.60.tgz", + "integrity": "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA==", "dev": true, "license": "BSD-3-Clause" }, @@ -1995,6 +2032,12 @@ "dev": true, "license": "MIT" }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2030,9 +2073,9 @@ } }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2043,31 +2086,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escalade": { @@ -2138,10 +2181,9 @@ } }, "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true, + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", "license": "MIT" }, "node_modules/fill-range": { @@ -2874,9 +2916,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -2894,7 +2936,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3155,6 +3197,12 @@ "dev": true, "license": "MIT" }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, "node_modules/prettier": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", @@ -3510,13 +3558,13 @@ } }, "node_modules/rollup": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz", - "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -3526,25 +3574,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.30.1", - "@rollup/rollup-android-arm64": "4.30.1", - "@rollup/rollup-darwin-arm64": "4.30.1", - "@rollup/rollup-darwin-x64": "4.30.1", - "@rollup/rollup-freebsd-arm64": "4.30.1", - "@rollup/rollup-freebsd-x64": "4.30.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.30.1", - "@rollup/rollup-linux-arm-musleabihf": "4.30.1", - "@rollup/rollup-linux-arm64-gnu": "4.30.1", - "@rollup/rollup-linux-arm64-musl": "4.30.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.30.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1", - "@rollup/rollup-linux-riscv64-gnu": "4.30.1", - "@rollup/rollup-linux-s390x-gnu": "4.30.1", - "@rollup/rollup-linux-x64-gnu": "4.30.1", - "@rollup/rollup-linux-x64-musl": "4.30.1", - "@rollup/rollup-win32-arm64-msvc": "4.30.1", - "@rollup/rollup-win32-ia32-msvc": "4.30.1", - "@rollup/rollup-win32-x64-msvc": "4.30.1", + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" } }, @@ -3687,6 +3736,29 @@ "solid-js": "*" } }, + "node_modules/solid-three": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/solid-three/-/solid-three-0.2.0.tgz", + "integrity": "sha512-CW3a8OFG8fGdEekdvri/Tx1wu1Mm6gUKI73bOxX0rA349WPoCYq3h1FNGjNS68ec5i2TvPpn7ZC4a3bq/S42fw==", + "license": "MIT", + "dependencies": { + "@types/three": "0.149.0", + "zustand": "^3.7.2" + }, + "peerDependencies": { + "solid-js": "*", + "three": "*" + } + }, + "node_modules/solid-three/node_modules/@types/three": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.149.0.tgz", + "integrity": "sha512-fgNBm9LWc65ER/W0cvoXdC0iMy7Ke9e2CONmEr6Jt8sDSY3sw4DgOubZfmdZ747dkPhbQrgRQAWwDEr2S/7IEg==", + "license": "MIT", + "dependencies": { + "@types/webxr": "*" + } + }, "node_modules/solid-toast": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/solid-toast/-/solid-toast-0.5.0.tgz", @@ -4076,11 +4148,73 @@ } }, "node_modules/three": { - "version": "0.172.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.172.0.tgz", - "integrity": "sha512-6HMgMlzU97MsV7D/tY8Va38b83kz8YJX+BefKjspMNAv0Vx6dxMogHOrnRl/sbMIs3BPUKijPqDqJ/+UwJbIow==", + "version": "0.175.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.175.0.tgz", + "integrity": "sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg==", "license": "MIT" }, + "node_modules/three-stdlib": { + "version": "2.35.15", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.35.15.tgz", + "integrity": "sha512-2XbhiOV7jVCchUoWxTmsj5dNhTe8eYb58Nyrfoa2F2yL+6h/+2VRaU2gENljRW1d/P1mk4/uwNzbdkKWV3hwzw==", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4178,15 +4312,18 @@ "license": "ISC" }, "node_modules/vite": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz", - "integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", + "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.24.2", - "postcss": "^8.4.49", - "rollup": "^4.23.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" }, "bin": { "vite": "bin/vite.js" @@ -4250,9 +4387,9 @@ } }, "node_modules/vite-plugin-glsl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.4.0.tgz", - "integrity": "sha512-mjT4AaU4qRmlpawgd0M2Qz72tvK4WF0ii2p0WbVRpr7ga6+cRScJUT3oIMv5coT8u/lqUe9u9T5+0zJLZ1uhug==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.4.1.tgz", + "integrity": "sha512-LkfLZeHdyl0drA71GmCCfcTeLuXyn/DYwDfjzQrupvNYVwdFRvb7nkCnCm0o9LZRKxS4/8r7E3KNM7KQjWrWWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4291,6 +4428,34 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitefu": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz", @@ -4480,6 +4645,23 @@ "engines": { "node": ">= 14" } + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } } } } diff --git a/pkg/client/frontend/package.json b/pkg/client/frontend/package.json index 7ecb5ec..c807e37 100644 --- a/pkg/client/frontend/package.json +++ b/pkg/client/frontend/package.json @@ -2,8 +2,8 @@ "name": "nukeship", "author": "Utkarsh Bhardwaj (Passeriform) ", "private": true, - "version": "1.0.0", "type": "module", + "version": "1.0.0", "scripts": { "start": "vite", "dev": "vite", @@ -18,15 +18,17 @@ "@tweenjs/tween.js": "^25.0.0", "solid-js": "^1.9.3", "solid-spinner": "^0.2.0", + "solid-three": "^0.2.0", "solid-toast": "^0.5.0", "tailwind-merge": "^2.6.0", - "three": "^0.172.0" + "three": "^0.175.0", + "three-stdlib": "^2.35.15" }, "devDependencies": { "@pyncz/tailwind-mask-image": "^2.0.0", "@trivago/prettier-plugin-sort-imports": "^5.2.1", "@types/node": "^22.10.5", - "@types/three": "^0.172.0", + "@types/three": "^0.175.0", "@xpd/tailwind-3dtransforms": "^1.0.3", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", diff --git a/pkg/client/frontend/src/components/Button.tsx b/pkg/client/frontend/src/components/Button.tsx index 6eb948d..8646472 100644 --- a/pkg/client/frontend/src/components/Button.tsx +++ b/pkg/client/frontend/src/components/Button.tsx @@ -7,17 +7,17 @@ interface ButtonProps { onClick?: (event: MouseEvent) => void } -const Button: VoidComponent = ({ onClick = () => undefined, class: cls = "", text = "" }) => { +const Button: VoidComponent = (props) => { return ( ) } diff --git a/pkg/client/frontend/src/components/NavButton.tsx b/pkg/client/frontend/src/components/NavButton.tsx index 2bfdc56..97a922a 100644 --- a/pkg/client/frontend/src/components/NavButton.tsx +++ b/pkg/client/frontend/src/components/NavButton.tsx @@ -1,4 +1,4 @@ -import { VoidComponent } from "solid-js" +import { VoidComponent, mergeProps } from "solid-js" import { twMerge } from "tailwind-merge" import Button from "./Button" @@ -9,15 +9,13 @@ interface NavButtonProps { position: "left" | "right" } -const NavButton: VoidComponent = ({ - onClick = () => undefined, - class: cls = "", - text = "", - position = "left", -}) => { +const NavButton: VoidComponent = (_props) => { + const props = mergeProps({ onClick: () => undefined }, _props) return ( - ) } diff --git a/pkg/client/frontend/src/components/VideoBackground.tsx b/pkg/client/frontend/src/components/VideoBackground.tsx index 6dfa61a..ac18d93 100644 --- a/pkg/client/frontend/src/components/VideoBackground.tsx +++ b/pkg/client/frontend/src/components/VideoBackground.tsx @@ -4,11 +4,11 @@ interface VideoBackgroundProps { src: string } -const VideoBackground: VoidComponent = ({ src }) => { +const VideoBackground: VoidComponent = (props) => { return (
) diff --git a/pkg/client/frontend/src/constants/sample.ts b/pkg/client/frontend/src/constants/sample.ts index 74ef1d7..c0df087 100644 --- a/pkg/client/frontend/src/constants/sample.ts +++ b/pkg/client/frontend/src/constants/sample.ts @@ -1,6 +1,6 @@ -import { TreeRawData } from "@game/tree" +import { RawDataStream } from "@game/Tree" -export const ExampleFS: TreeRawData = { +export const ExampleFS: RawDataStream = { label: "C:", children: [ { diff --git a/pkg/client/frontend/src/constants/statics.ts b/pkg/client/frontend/src/constants/statics.ts index 64f1f6d..ddf2c33 100644 --- a/pkg/client/frontend/src/constants/statics.ts +++ b/pkg/client/frontend/src/constants/statics.ts @@ -1,22 +1,5 @@ -import { Quaternion, Vector3 } from "three" -import { TweenTransform } from "@constants/types" +import { Vector3 } from "three" export const X_AXIS = Object.freeze(new Vector3(1, 0, 0)) export const Y_AXIS = Object.freeze(new Vector3(0, 1, 0)) export const Z_AXIS = Object.freeze(new Vector3(0, 0, 1)) - -export const ELEVATION_FORWARD_QUATERNION = Object.freeze(new Quaternion(0, 0, 0, 1).normalize()) - -export const STATICS = { - DIRECTIONAL_LIGHT: { - position: Object.freeze(new Vector3(5, 5, 5)), - }, - SELF: { - position: Object.freeze(new Vector3(2, 0, 0)), - rotation: Object.freeze(new Quaternion(0, 1, 0, 1).normalize()), - }, - OPPONENT: { - position: Object.freeze(new Vector3(-2, 0, 0)), - rotation: Object.freeze(new Quaternion(0, -1, 0, 1).normalize()), - }, -} satisfies Record> diff --git a/pkg/client/frontend/src/game/Game.tsx b/pkg/client/frontend/src/game/Game.tsx new file mode 100644 index 0000000..ec1bebd --- /dev/null +++ b/pkg/client/frontend/src/game/Game.tsx @@ -0,0 +1,216 @@ +import { TourControlsDrillEvent } from "@passeriform/three-tour-controls" +import { Group as TweenGroup } from "@tweenjs/tween.js" +import { VoidComponent, createEffect, extend, useFrame } from "solid-three" +import { Mesh, Object3D, Quaternion, QuaternionTuple, Vector3Tuple } from "three" +import { ExampleFS } from "@constants/sample" +import { FocusType, ViewType } from "@constants/types" +import { boundsFromObjects, boundsFromPoints } from "@utility/bounds" +import { tweenOpacity } from "@utility/tween" +import TourControls from "./TourControls" +import Tree from "./Tree" + +const AMBIENT_LIGHT_COLOR = 0x193751 +const AMBIENT_LIGHT_INTENSITY = 2 +const DIRECTIONAL_LIGHT_COLOR = 0xffffff +const DIRECTIONAL_LIGHT_INTENSITY = 2 +const DIRECTIONAL_LIGHT_POSITION = [5, 5, 5] as Vector3Tuple +const SELF_TREE_POSITION = [2, 0, 0] as Vector3Tuple +const SELF_TREE_ROTATION = [0, 1, 0, 1] as QuaternionTuple +const OPPONENT_TREE_POSITION = [-2, 0, 0] as Vector3Tuple +const OPPONENT_TREE_ROTATION = [0, -1, 0, 1] as QuaternionTuple +const ARCH_CONTROLS_ELEVATION_FORWARD_ROTATION = [0, 0, 0, 1] as QuaternionTuple +const ARCH_CONTROLS_OFFSET = { + [ViewType.ELEVATION]: 4, + [ViewType.PLAN]: 2, +} +const TARGET_CONTROLS_OFFSET = 1 +const PLAN_MATCH_OPACITY_MAP: Record = { + [-1]: 0.2, + [1]: 0, + [0]: 1, +} + +extend({ TourControls }) + +type GameProps = { + view: ViewType + focus: FocusType + tweenGroup: TweenGroup +} + +const Game: VoidComponent = (props) => { + let selfTree: Object3D + let opponentTree: Object3D + let pushTarget: (target: Object3D) => void + let getSelfTreeNodeMeshes: () => Mesh[][] + let getSelfTreeNodePositions: () => Vector3Tuple[][] + let getOpponentTreeNodeMeshes: () => Mesh[][] + let getOpponentTreeNodePositions: () => Vector3Tuple[][] + + const getFocussedTree = () => { + if (!selfTree! || !opponentTree!) { + return undefined + } + + return ( + (props.focus === FocusType.SELF && selfTree!) || + (props.focus === FocusType.OPPONENT && opponentTree!) || + undefined + ) + } + + const getFocussedTreeNodeMeshes = () => { + if (!getSelfTreeNodeMeshes! || !getOpponentTreeNodeMeshes!) { + return undefined + } + + return ( + (props.focus === FocusType.SELF && getSelfTreeNodeMeshes!()) || + (props.focus === FocusType.OPPONENT && getOpponentTreeNodeMeshes!()) || + undefined + ) + } + + const getFocussedTreeNodePositions = () => { + if (!getSelfTreeNodePositions! || !getOpponentTreeNodePositions!) { + return undefined + } + + return ( + (props.focus === FocusType.SELF && getSelfTreeNodePositions!()) || + (props.focus === FocusType.OPPONENT && getOpponentTreeNodePositions!()) || + undefined + ) + } + + const getInteractables = () => { + const focusedTree = getFocussedTree() + return focusedTree ? [focusedTree] : [] + } + + const getBoundPoses = () => { + if (!selfTree! || !opponentTree! || !getSelfTreeNodeMeshes! || !getOpponentTreeNodeMeshes!) { + return + } + + const focusedTree = getFocussedTree() + + if (!focusedTree) { + return [ + { + bounds: boundsFromObjects(selfTree, opponentTree), + quaternion: new Quaternion(...ARCH_CONTROLS_ELEVATION_FORWARD_ROTATION), + }, + ] + } + + const targetTreeNodePositions = getFocussedTreeNodePositions() + + if (!targetTreeNodePositions) { + return + } + + return props.view === ViewType.ELEVATION + ? [ + { + bounds: boundsFromObjects(focusedTree), + quaternion: new Quaternion(...ARCH_CONTROLS_ELEVATION_FORWARD_ROTATION), + }, + ] + : targetTreeNodePositions.map((level) => ({ + bounds: boundsFromPoints(...level), + quaternion: focusedTree.quaternion.clone(), + })) + } + + const resetOpacities = () => { + if (!getSelfTreeNodeMeshes! || !getOpponentTreeNodeMeshes!) { + return + } + + getSelfTreeNodeMeshes().forEach((level) => level.forEach((mesh) => tweenOpacity(props.tweenGroup, mesh, 1))) + getOpponentTreeNodeMeshes().forEach((level) => level.forEach((mesh) => tweenOpacity(props.tweenGroup, mesh, 1))) + } + + useFrame(() => { + props.tweenGroup.update() + }, -1) + + createEffect(() => { + if (props.focus === FocusType.NONE) { + resetOpacities() + return + } + + if (props.view === ViewType.ELEVATION) { + resetOpacities() + return + } + }) + + return ( + <> + + + + getFocussedTreeNodeMeshes()?.forEach((level, levelIdx) => + level.forEach((mesh) => + tweenOpacity( + props.tweenGroup, + mesh, + event.historyIdx === -1 + ? 1 + : PLAN_MATCH_OPACITY_MAP[Math.sign(event.historyIdx - levelIdx)]!, + ), + ), + ) + } + /> + {/* { + resetOpacities() + tweenOpacity(event.tweenGroup, event.intersect as any, 1) + }} + /> */} + props.focus === FocusType.SELF && pushTarget!(event.object as any)} + onPointerOver={() => undefined} + onPointerOut={() => undefined} + onPointerMissed={() => undefined} + /> + props.focus === FocusType.OPPONENT && pushTarget!(event.object as any)} + onPointerOver={() => undefined} + onPointerOut={() => undefined} + onPointerMissed={() => undefined} + /> + + ) +} + +export default Game diff --git a/pkg/client/frontend/src/game/GameRenderer.tsx b/pkg/client/frontend/src/game/GameRenderer.tsx new file mode 100644 index 0000000..ea8acb4 --- /dev/null +++ b/pkg/client/frontend/src/game/GameRenderer.tsx @@ -0,0 +1,40 @@ +import { Group as TweenGroup } from "@tweenjs/tween.js" +import { Canvas, VoidComponent, onMount } from "solid-three" +import { getWebGL2ErrorMessage, isWebGL2Available } from "three-stdlib" +import { FocusType, ViewType } from "@constants/types" +import Game from "./Game" + +export const CameraType = { + PERSPECTIVE: "PERSPECTIVE", + ORTHOGRAPHIC: "ORTHOGRAPHIC", +} + +export type CameraType = (typeof CameraType)[keyof typeof CameraType] + +interface GameRendererProps { + cameraType: CameraType + view: ViewType + focus: FocusType + onIncompatible: (renderableError: HTMLDivElement) => void +} + +const GameRenderer: VoidComponent = (props) => { + const tweenGroup = new TweenGroup() + + onMount(() => { + if (!isWebGL2Available()) { + props.onIncompatible(getWebGL2ErrorMessage()) + return + } + + document.addEventListener("contextmenu", (event) => event.preventDefault()) + }) + + return ( + + + + ) +} + +export default GameRenderer diff --git a/pkg/client/frontend/src/game/TargetControls.tsx b/pkg/client/frontend/src/game/TargetControls.tsx new file mode 100644 index 0000000..29fa050 --- /dev/null +++ b/pkg/client/frontend/src/game/TargetControls.tsx @@ -0,0 +1,114 @@ +import { + Component, + createEffect, + createMemo, + extend, + mergeProps, + on, + onCleanup, + splitProps, + untrack, + useFrame, + useThree, +} from "solid-three" +import { Mesh, Object3D, PerspectiveCamera } from "three" +import { + TargetControlsChangeEvent, + TargetControlsDeselectEvent, + TargetControls as TargetControlsImpl, + TargetControlsSelectEvent, +} from "./TargetControlsImpl" +import { ControlsNode } from "./controls" + +export type TargetControlsProps = ControlsNode & { + interactables?: Object3D[] + pushTargetRef?: (target: Mesh) => void + onChange?: (e: TargetControlsChangeEvent) => void + onSelect?: (e: TargetControlsSelectEvent) => void + onDeselect?: (e: TargetControlsDeselectEvent) => void +} + +// TODO: Add key events according to OrbitControls. +const TargetControls: Component = (_props) => { + const [props, rest] = splitProps(mergeProps({ interactables: [] as Object3D[] }, _props) as any, [ + "ref", + "interactables", + "pushTargetRef", + "camera", + "makeDefault", + "regress", + "domElement", + "onChange", + "onSelect", + "onDeselect", + ]) + + const store = useThree() + const camera = () => props.camera || store().camera + const domElement = () => (props.domElement || store().events.connected || store().gl.domElement) as HTMLElement + const controls = createMemo(() => new TargetControlsImpl(camera() as unknown as PerspectiveCamera, domElement())) + + useFrame(() => { + if (controls().enabled) { + controls().update() + } + }, -1) + + createEffect( + on( + () => [domElement(), props.regress, controls(), store().invalidate], + () => { + controls().connect(domElement()) + onCleanup(() => void controls().dispose()) + }, + ), + ) + + createEffect(() => { + controls().setInteractables(props.interactables) + }) + + createEffect(() => { + props.pushTargetRef = controls().pushTarget + }) + + createEffect(() => { + const callback = (e: TargetControlsChangeEvent) => { + store().invalidate() + if (props.regress) store().performance.regress() + if (props.onChange) props.onChange(e) + } + + const onSelectCb = (e: TargetControlsSelectEvent) => { + if (props.onSelect) props.onSelect(e) + } + + const onDeselectCb = (e: TargetControlsDeselectEvent) => { + if (props.onDeselect) props.onDeselect(e) + } + + controls().addEventListener("select", onSelectCb) + controls().addEventListener("deselect", onDeselectCb) + controls().addEventListener("change", callback) + + onCleanup(() => { + controls().removeEventListener("select", onSelectCb) + controls().removeEventListener("deselect", onDeselectCb) + controls().removeEventListener("change", callback) + }) + }) + + createEffect(() => { + if (props.makeDefault) { + const old = untrack(() => store().controls) + store().set({ controls: controls() as any }) + onCleanup(() => store().set({ controls: old })) + } + }) + + return +} + +// TODO: Roll out as a solid-three control called TargetControls. + +export default TargetControls diff --git a/pkg/client/frontend/src/game/targetControls.ts b/pkg/client/frontend/src/game/TargetControlsImpl.ts similarity index 59% rename from pkg/client/frontend/src/game/targetControls.ts rename to pkg/client/frontend/src/game/TargetControlsImpl.ts index 8648cfd..cea45e0 100644 --- a/pkg/client/frontend/src/game/targetControls.ts +++ b/pkg/client/frontend/src/game/TargetControlsImpl.ts @@ -1,31 +1,36 @@ -import { Group as TweenGroup } from "@tweenjs/tween.js" -import { Controls, Object3D, OrthographicCamera, PerspectiveCamera, Quaternion, Raycaster, Vector2 } from "three" -import { ELEVATION_FORWARD_QUATERNION, Z_AXIS } from "@constants/statics" +import { Easing, Tween, Group as TweenGroup } from "@tweenjs/tween.js" +import { Controls, Event, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, Quaternion } from "three" +import { Z_AXIS } from "@constants/statics" import { TweenTransform } from "@constants/types" import { lookAtFromQuaternion } from "@utility/camera" import { getWorldPose } from "@utility/pureTransform" import { averageQuaternions } from "@utility/quaternion" -import { tweenTransform } from "@utility/tween" + +const ELEVATION_FORWARD_QUATERNION = Object.freeze(new Quaternion(0, 0, 0, 1)) type TargetControlsEventMap = { select: { - intersect: Object3D + intersect: Mesh tweenGroup: TweenGroup } deselect: { tweenGroup: TweenGroup } + change: {} } +export type TargetControlsSelectEvent = TargetControlsEventMap["select"] & Event<"select", TargetControls> +export type TargetControlsDeselectEvent = TargetControlsEventMap["deselect"] & Event<"deselect", TargetControls> +export type TargetControlsChangeEvent = TargetControlsEventMap["change"] & Event<"change", TargetControls> + +// TODO: Remove usage of examples and addons subdirectory and use three-stdlib for versioned support. // TODO: Remove usage of private temporary variables, instead use immutable utilities. // TODO: Rework according to archControls usage. export class TargetControls extends Controls { private _lastSelected: Object3D | undefined - private pointer: Vector2 private preloadedRotation: Quaternion private tweenGroup: TweenGroup - private raycaster: Raycaster private historyIdx: number private history: TweenTransform[] private transitioning: boolean @@ -36,25 +41,36 @@ export class TargetControls extends Controls { this.historyIdx = -1 } - private loadRotation() { - const rotations = this.targets.map((target) => getWorldPose(target)[1]) + private loadRotation(interactables: Object3D[]) { + const rotations = interactables.map((target) => getWorldPose(target)[1]) this.preloadedRotation = averageQuaternions(...rotations) - this.preloadedRotation.slerp(lookAtFromQuaternion(this.object, ELEVATION_FORWARD_QUATERNION.clone()), 0.5) + this.preloadedRotation.slerp(lookAtFromQuaternion(this.object, ELEVATION_FORWARD_QUATERNION), 0.5) } private animate(tweenTarget: TweenTransform) { - this.transitioning = true - this.tweenGroup.removeAll() - tweenTransform(this.tweenGroup, this.object, tweenTarget, { onComplete: () => (this.transitioning = false) }) - } - - private onMouseMove(event: MouseEvent) { if (!this.enabled) { return } - this.pointer.x = (event.clientX / window.innerWidth) * 2 - 1 - this.pointer.y = -(event.clientY / window.innerHeight) * 2 + 1 + this.transitioning = true + + const qFrom = this.object.quaternion.clone() + const qTo = tweenTarget.rotation.normalize() + + this.tweenGroup.add( + new Tween({ position: this.object.position.clone(), time: 0 }) + .to({ position: tweenTarget.position, time: 1 }, 400) + .easing(Easing.Cubic.InOut) + .onUpdate(({ position, time }) => { + this.dispatchEvent({ type: "change" }) + this.object.position.copy(position) + this.object.quaternion.slerpQuaternions(qFrom, qTo, time) + }) + .onComplete(() => { + this.transitioning = false + }) + .start(), + ) } private onMouseWheel(event: WheelEvent) { @@ -75,7 +91,7 @@ export class TargetControls extends Controls { this.historyIdx-- } - this.animate(this.history[this.historyIdx]) + this.animate(this.history[this.historyIdx]!) } private onMouseDown(event: MouseEvent) { @@ -87,7 +103,7 @@ export class TargetControls extends Controls { if (event.button === 2) { if (this.history.length) { - this.animate(this.history[0]) + this.animate(this.history[0]!) } this.dispatchEvent({ @@ -99,19 +115,39 @@ export class TargetControls extends Controls { return } + } + + constructor( + public override object: PerspectiveCamera | OrthographicCamera, + public override domElement: HTMLElement | null = null, + ) { + super(object, domElement) - this.raycaster.setFromCamera(this.pointer, this.object) + this.preloadedRotation = new Quaternion() + this.tweenGroup = new TweenGroup() + this.history = [] + this.historyIdx = -1 + this.transitioning = false + this.cameraOffset = 1 - const intersects = this.raycaster.intersectObjects(this.targets, true) + if (domElement) { + this.connect(domElement) + } + this.update() + } - const [matched] = intersects.map((intersection) => intersection.object).filter(this.matcher) + override connect(element: HTMLElement) { + element.addEventListener("mousedown", (event) => this.onMouseDown(event)) + element.addEventListener("wheel", (event) => this.onMouseWheel(event)) + } - if (!matched || matched === this._lastSelected) { + pushTarget(target: Mesh) { + if (target === this._lastSelected) { return } - this._lastSelected = matched - const [position] = getWorldPose(matched) + this._lastSelected = target + const [position] = getWorldPose(target) // TODO: Make this reliant on getWorldQuaternion. const tweenTarget = { @@ -132,7 +168,7 @@ export class TargetControls extends Controls { this.dispatchEvent({ type: "select", - intersect: matched, + intersect: target, tweenGroup: this.tweenGroup, }) } @@ -142,53 +178,24 @@ export class TargetControls extends Controls { this.dispatchEvent({ type: "select", - intersect: matched, + intersect: target, tweenGroup: this.tweenGroup, }) this.animate(tweenTarget) } - constructor( - private targets: Object3D[], - public object: PerspectiveCamera | OrthographicCamera, - private matcher: (item: Object3D, index: number) => boolean = () => true, - public domElement: HTMLElement | null = null, - ) { - super(object, domElement) - - this.pointer = new Vector2() - this.preloadedRotation = new Quaternion() - this.tweenGroup = new TweenGroup() - this.raycaster = new Raycaster() - this.history = [] - this.historyIdx = -1 - this.transitioning = false - this.cameraOffset = 1 - - this.connect() - this.update() - } - - connect() { - document.addEventListener("mousemove", (event) => this.onMouseMove(event)) - document.addEventListener("mousedown", (event) => this.onMouseDown(event)) - document.addEventListener("wheel", (event) => this.onMouseWheel(event)) - } - - setTargets(targets: Object3D[]) { - if (!targets.length) { + setInteractables(interactables: Object3D[]) { + if (!interactables.length) { throw Error("At least one target is required to be assigned for target controls.") } - this.targets = targets - this.resetHistory() - this.loadRotation() + this.loadRotation(interactables) } - update(time?: number) { + override update(time?: number) { this.tweenGroup.update(time) } diff --git a/pkg/client/frontend/src/game/TourControls.tsx b/pkg/client/frontend/src/game/TourControls.tsx new file mode 100644 index 0000000..53fdb8a --- /dev/null +++ b/pkg/client/frontend/src/game/TourControls.tsx @@ -0,0 +1,98 @@ +import TourControlsImpl, { + BoundPose, + TourControlsChangeEvent, + TourControlsDrillEvent, +} from "@passeriform/three-tour-controls" +import { mergeProps } from "solid-js" +import { + Component, + createEffect, + createMemo, + on, + onCleanup, + splitProps, + untrack, + useFrame, + useThree, +} from "solid-three" +import { PerspectiveCamera } from "three" +import { ControlsNode } from "./controls" + +export type TourControlsProps = ControlsNode & { + boundPoses: BoundPose[] + onChange?: (e?: TourControlsChangeEvent) => void + onDrill?: (e: TourControlsDrillEvent) => void +} + +// TODO: Add key events according to OrbitControls. +const TourControls: Component = (_props) => { + const [props, rest] = splitProps(mergeProps({ boundPoses: [] as BoundPose[] }, _props), [ + "ref", + "boundPoses", + "camera", + "makeDefault", + "regress", + "domElement", + "onChange", + "onDrill", + ]) + + const store = useThree() + const camera = () => props.camera || store().camera + const domElement = () => (props.domElement || store().events.connected || store().gl.domElement) as HTMLElement + const controls = createMemo(() => new TourControlsImpl(camera() as unknown as PerspectiveCamera, [], domElement())) + + useFrame(() => { + if (controls().enabled) { + controls().update() + } + }, -1) + + createEffect( + on( + () => [domElement(), props.regress, controls(), store().invalidate], + () => { + controls().connect(domElement()) + onCleanup(() => void controls().dispose()) + }, + ), + ) + + createEffect(() => { + controls().setBoundPoses(props.boundPoses) + }) + + createEffect(() => { + const callback = (e: TourControlsChangeEvent) => { + store().invalidate() + if (props.regress) store().performance.regress() + if (props.onChange) props.onChange(e) + } + + const onDrillCb = (e: TourControlsDrillEvent) => { + if (props.onDrill) props.onDrill(e) + } + + controls().addEventListener("drill", onDrillCb) + controls().addEventListener("change", callback) + + onCleanup(() => { + controls().removeEventListener("drill", onDrillCb) + controls().removeEventListener("change", callback) + }) + }) + + createEffect(() => { + if (props.makeDefault) { + const old = untrack(() => store().controls) + store().set({ controls: controls() as any }) + onCleanup(() => store().set({ controls: old })) + } + }) + + return <> +} + +// TODO: Only keep one of the controls active (arch/target) at a time to avoid history override and conflicts. + +export default TourControls diff --git a/pkg/client/frontend/src/game/Tree.tsx b/pkg/client/frontend/src/game/Tree.tsx new file mode 100644 index 0000000..1feaf3f --- /dev/null +++ b/pkg/client/frontend/src/game/Tree.tsx @@ -0,0 +1,188 @@ +import { For, IntersectionEvent, Show, VoidComponent, createEffect, createSignal, mergeProps } from "solid-three" +import { Mesh, Object3D, QuaternionTuple, Vector3Tuple } from "three" + +const COLORS = [0x7b68ee, 0xda1d81, 0xcccccc, 0x193751] as const + +// TODO: Fixup according to client structure. +export type RawDataStream = { + label: string + children: RawDataStream[] +} + +export type TreeProps = { + dataStream: RawDataStream + colorSeed?: number + position?: Vector3Tuple + rotation?: QuaternionTuple + ref?: Object3D | ((instance: Object3D) => void) + getNodeMeshesRef?: () => Mesh[][] + getNodePositionsRef?: () => Vector3Tuple[][] + onClick?: (event: IntersectionEvent) => void + onPointerOver?: (event: IntersectionEvent) => void + onPointerOut?: (event: IntersectionEvent) => void + onPointerMissed?: (event: MouseEvent) => void +} + +type SaplingProps = { + root: RawDataStream + depth: number + colorSeed?: number + position?: Vector3Tuple + quaternion?: QuaternionTuple + setLevelNodeMeshes?: (level: number, idx: number, mesh: Mesh) => void + setLevelNodePositions?: (level: number, positions: Vector3Tuple[]) => void + onClick?: (event: IntersectionEvent) => void + onPointerOver?: (event: IntersectionEvent) => void + onPointerOut?: (event: IntersectionEvent) => void + onPointerMissed?: (event: MouseEvent) => void +} + +const DEPTH_OFFSET = 4 +const LATERAL_OFFSET = 2 + +const getColor = (seed: number) => { + const color = COLORS[seed % COLORS.length]! + return color +} + +const getPositions = (childCount: number, radius: number, depthModifier: number) => { + const angle = (2 * Math.PI) / childCount + + return new Array(childCount).fill(0).map((_, idx) => { + const unitX = radius * Math.cos(angle * idx) + const unitY = radius * Math.sin(angle * idx) + + const x = (unitX * LATERAL_OFFSET) / depthModifier + const y = (unitY * LATERAL_OFFSET) / depthModifier + const z = DEPTH_OFFSET / depthModifier + + return [x, y, z] as Vector3Tuple + }) +} + +const Sapling: VoidComponent = (_props) => { + const props = mergeProps( + { + colorSeed: 0, + position: [0, 0, 0] as Vector3Tuple, + rotation: [0, 0, 0, 1] as QuaternionTuple, + }, + _props, + ) as Required + + let nodeMeshRef: Mesh + + const childPositions = getPositions(props.root.children.length, 1, props.depth) + + createEffect(() => { + if (!nodeMeshRef!) { + return + } + props.setLevelNodeMeshes?.(props.depth - 1, 0, nodeMeshRef) + }) + + createEffect(() => { + props.setLevelNodePositions?.(props.depth, childPositions) + }) + + return ( + <> + + + + + + + {(child, idx) => ( + + )} + + + + + + {(_, idx) => ( + + )} + + + + + ) +} + +// TODO: Convert to an explicit intrinsic element returning a primitive. Apply other options on top of primitive +const Tree: VoidComponent = (_props) => { + const props = mergeProps( + { + colorSeed: 0, + position: [0, 0, 0] as Vector3Tuple, + rotation: [0, 0, 0, 1] as QuaternionTuple, + }, + _props, + ) as Required + + const [nodeMeshes, setNodeMeshes] = createSignal([]) + const [nodePositions, setNodePositions] = createSignal([]) + + props.getNodeMeshesRef = nodeMeshes + props.getNodePositionsRef = nodePositions + + const setLevelNodeMeshes = (level: number, idx: number, nodeMesh: Mesh) => { + setNodeMeshes((prev) => { + if (!prev[level]) { + prev[level] = [] + } + prev[level][idx] = nodeMesh + return [...prev] + }) + } + + const setLevelNodePositions = (level: number, nodePositions: Vector3Tuple[]) => { + setNodePositions((prev) => { + prev[level] = nodePositions + return [...prev] + }) + } + + createEffect(() => { + setLevelNodePositions(0, [props.position]) + }) + + return ( + + + + ) +} + +export default Tree diff --git a/pkg/client/frontend/src/game/archControls.ts b/pkg/client/frontend/src/game/archControls.ts deleted file mode 100644 index 4269697..0000000 --- a/pkg/client/frontend/src/game/archControls.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Group as TweenGroup } from "@tweenjs/tween.js" -import { BaseEvent, Box3, Controls, MathUtils, OrthographicCamera, PerspectiveCamera, Quaternion } from "three" -import { Z_AXIS } from "@constants/statics" -import { TweenTransform } from "@constants/types" -import { unpackBounds } from "@utility/bounds" -import { lookAtFromQuaternion } from "@utility/camera" -import { tweenTransform } from "@utility/tween" - -// TODO: Animate camera position along a pivot arc when rotating, instead of linear path. -// TODO: Add rotation to FSTree on panning. -// TODO: Add common history between arch and target controls. - -type ArchControlsEventMap = { - drill: { - historyIdx: number - } -} - -export type DrillEvent = BaseEvent<"drill"> & ArchControlsEventMap["drill"] - -// TODO: Accept tweenGroup from caller - -export class ArchControls extends Controls { - private boundSet: Box3[] - private preloadedRotation: Quaternion - private tweenGroup: TweenGroup - private transitioning: boolean - private history: TweenTransform[] - private historyIdx: number - public cameraOffset: number - - private animate(tweenTarget: TweenTransform) { - if (!this.enabled) { - return - } - - this.transitioning = true - tweenTransform(this.tweenGroup, this.object, tweenTarget, { onComplete: () => (this.transitioning = false) }) - } - - private updateToFitScreen() { - this.history = this.boundSet.map((pose) => { - const [center, size] = unpackBounds(pose) - - const heightToFit = size.x / size.y < this.object.aspect ? size.y : size.x / this.object.aspect - const cameraDistance = - (heightToFit * 0.5) / Math.tan(this.object.fov * MathUtils.DEG2RAD * 0.5) + this.cameraOffset - - const rotation = lookAtFromQuaternion(this.object, this.preloadedRotation) - const position = center.add(Z_AXIS.clone().applyQuaternion(rotation).multiplyScalar(cameraDistance)).clone() - - return { position, rotation } - }) - } - - // TODO: Fix resize recalculating the camera position if snapControls is enabled. - private onResize() { - this.object.aspect = window.innerWidth / window.innerHeight - - this.object.updateProjectionMatrix() - - this.updateToFitScreen() - - this.animate(this.history[this.historyIdx]) - } - - private onMouseWheel(event: WheelEvent) { - if (!this.enabled || !this.history.length || this.transitioning) { - return - } - - if ( - (event.deltaY < 0 && this.historyIdx === this.history.length - 1) || - (event.deltaY > 0 && this.historyIdx === 0) - ) { - return - } - - if (event.deltaY < 0) { - this.historyIdx++ - } else if (event.deltaY > 0) { - this.historyIdx-- - } - - this.dispatchEvent({ - type: "drill", - historyIdx: this.historyIdx, - }) - - this.animate(this.history[this.historyIdx]) - } - - constructor( - public object: PerspectiveCamera, - public domElement: HTMLElement | null = null, - ) { - if ("isOrthographicCamera" in object && (object as unknown as OrthographicCamera).isOrthographicCamera) { - throw Error("Arch controls currently only works for perspective camera.") - } - - super(object, domElement) - - this.boundSet = [] - this.preloadedRotation = new Quaternion() - this.tweenGroup = new TweenGroup() - this.cameraOffset = 4 - this.transitioning = false - this.history = [] - this.historyIdx = -1 - - this.connect() - this.update() - } - - connect() { - ;(this.domElement ?? window).addEventListener("resize", () => this.onResize()) - document.addEventListener("wheel", (event) => this.onMouseWheel(event)) - } - - setPoses(boundSet: Box3[], rotation: Quaternion) { - this.preloadedRotation = rotation - this.boundSet = boundSet - - this.historyIdx = 0 - - this.updateToFitScreen() - - this.animate(this.history[this.historyIdx]) - } - - update(time?: number) { - this.tweenGroup.update(time) - } - - clear() { - this.tweenGroup.removeAll() - super.dispose() - return - } -} diff --git a/pkg/client/frontend/src/game/controls.d.ts b/pkg/client/frontend/src/game/controls.d.ts new file mode 100644 index 0000000..e81e371 --- /dev/null +++ b/pkg/client/frontend/src/game/controls.d.ts @@ -0,0 +1,23 @@ +import { Camera, Node, Overwrite, Vector3 } from "solid-three" +import { Controls, Event } from "three" +import TourControls, { TourControlsProps } from "./TourControls" + +export type ControlsNode, P> = Overwrite< + Node, + { + target?: Vector3 + camera?: Camera + domElement?: HTMLElement + regress?: boolean + makeDefault?: boolean + onChange?: (e?: Event) => void + } +> + +declare module "solid-three" { + namespace JSX { + interface IntrinsicElements { + tourControls: TourControlsProps + } + } +} diff --git a/pkg/client/frontend/src/game/tree.ts b/pkg/client/frontend/src/game/tree.ts deleted file mode 100644 index 942fced..0000000 --- a/pkg/client/frontend/src/game/tree.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Group as TweenGroup } from "@tweenjs/tween.js" -import { - Box3, - BufferGeometry, - Color, - Line, - LineBasicMaterial, - Mesh, - MeshLambertMaterial, - Group as ObjectGroup, - SphereGeometry, - Vector3, -} from "three" -import { Z_AXIS } from "@constants/statics" - -// TODO: Fixup according to client structure. -export type TreeRawData = { - label: string - children: TreeRawData[] -} - -// TODO: Add Node as a renderable class. - -const DEPTH_OFFSET = 4 -const LATERAL_OFFSET = 2 -const COLORS = [0x7b68ee, 0xda1d81, 0xcccccc, 0x193751] as const - -export class TreeNode extends Mesh { - private static NODE_GEOMETRY = new SphereGeometry(0.1, 64, 64) - public static MESH_NAME = "TREE_NODE" - - override name = TreeNode.MESH_NAME - - constructor(color: Color) { - const geometry = TreeNode.NODE_GEOMETRY.clone() - const material = new MeshLambertMaterial({ color, transparent: true }) - - super(geometry, material) - } - - glow(value: boolean, tweenGroup: TweenGroup) {} -} - -export class Tree extends Mesh { - private static CONNECTOR_MATERIALS = COLORS.map((color) => new LineBasicMaterial({ color, transparent: true })) - - private static splitChildrenEvenly(itemCount: number) { - if (itemCount === 1) { - return [[0, 0]] as const - } else if (itemCount === 2) { - return [ - [-1, -1], - [1, 1], - ] as const - } else if (itemCount <= 4) { - return [ - [-1, -1], - [-1, 1], - [1, -1], - [1, 1], - ] as const - } else if (itemCount <= 9) { - return [ - [-1, -1], - [-1, 1], - [1, -1], - [1, 1], - [-1, 0], - [0, -1], - [0, 1], - [1, 0], - [0, 0], - ] as const - } else { - return [[0, 0]] as const - } - } - - constructor( - private connectorLevels: Line[][] = [], - public levels: TreeNode[][] = [], - public levelBounds: Box3[] = [], - ) { - super() - } - - private generateRenderNodes(node: TreeRawData, depth: number, colorSeed: number) { - // TODO: Use InstancedMesh with changing instanceColor instead of new meshes - const nodeMesh = new TreeNode(new Color(COLORS[(colorSeed + depth - 1) % COLORS.length])) - - // Add level collection - if (this.levels.length < depth) { - this.levels.push([]) - } - if (this.connectorLevels.length < depth) { - this.connectorLevels.push([]) - } - - // Add level meshes - this.levels[depth - 1].push(nodeMesh) - - // Children meshes - const childrenGroup = new ObjectGroup() - const childrenMeshes = node.children.map( - (node): ObjectGroup => this.generateRenderNodes(node, depth + 1, colorSeed), - ) - - if (childrenMeshes.length) { - const positions = Tree.splitChildrenEvenly(childrenMeshes.length) - childrenMeshes.forEach((node, idx) => - node.position.set( - (positions[idx][0] * LATERAL_OFFSET) / depth, - (positions[idx][1] * LATERAL_OFFSET) / depth, - DEPTH_OFFSET / depth, - ), - ) - childrenGroup.add(...childrenMeshes) - } - - // Connector meshes - const connectorGroup = new ObjectGroup() - const connectorMaterial = Tree.CONNECTOR_MATERIALS[(colorSeed + depth - 1) % COLORS.length].clone() - const connectorMeshes = childrenMeshes.map((node) => { - const connectorGeometry = new BufferGeometry().setFromPoints([nodeMesh.position, node.position]) - const line = new Line(connectorGeometry, connectorMaterial) - return line - }) - if (connectorMeshes.length) { - connectorGroup.add(...connectorMeshes) - } - - // Add connector level meshes - this.connectorLevels[depth - 1].push(...connectorMeshes) - - // Parent group - const parent = new ObjectGroup() - parent.add(nodeMesh, childrenGroup, connectorGroup) - - return parent - } - - get normal() { - return Z_AXIS.clone().applyQuaternion(this.quaternion) - } - - get levelCount() { - return this.levels.length - } - - traverseLevelOrder(levelTransform: (mesh: TreeNode | Line, levelIdx: number) => void) { - this.levels.forEach((level, idx) => - level.forEach((node) => { - levelTransform(node, idx) - }), - ) - - this.connectorLevels.forEach((level, idx) => - level.forEach((line) => { - levelTransform(line, idx) - }), - ) - } - - recomputeBounds() { - this.levelBounds = this.levels.map((level) => { - const boundingBox = new Box3() - - const meshCenters = level.map((mesh) => { - const worldPosition = new Vector3() - mesh.getWorldPosition(worldPosition) - return worldPosition - }) - - boundingBox.setFromPoints(meshCenters) - - return boundingBox - }) - } - - setFromRawData(data: TreeRawData, colorSeed = 0) { - this.add(this.generateRenderNodes(data, 1, colorSeed)) - this.recomputeBounds() - return this - } -} diff --git a/pkg/client/frontend/src/index.tsx b/pkg/client/frontend/src/index.tsx index ddb5080..23dfcd1 100644 --- a/pkg/client/frontend/src/index.tsx +++ b/pkg/client/frontend/src/index.tsx @@ -1,10 +1,10 @@ /* @refresh reload */ import "@thisbeyond/solid-select/style.css" import { render } from "solid-js/web" -import App from "./App" +import GameBoard from "@pages/GameBoard" import "./assets/stylesheets/dropdown-overrides.css" import "./index.css" const root = document.getElementById("root") -render(() => , root!) +render(() => , root!) diff --git a/pkg/client/frontend/src/pages/GameBoard.tsx b/pkg/client/frontend/src/pages/GameBoard.tsx index c9165fb..dd12ed3 100644 --- a/pkg/client/frontend/src/pages/GameBoard.tsx +++ b/pkg/client/frontend/src/pages/GameBoard.tsx @@ -1,238 +1,35 @@ -import { useParams } from "@solidjs/router" -import { Group as TweenGroup } from "@tweenjs/tween.js" -import { Show, Switch, VoidComponent, createEffect, createSignal, onCleanup, onMount } from "solid-js" -import { Match } from "solid-js" +import { Match, Show, Switch, VoidComponent, createEffect, createSignal } from "solid-js" import toast from "solid-toast" -import { Raycaster, Vector2 } from "three" -import WebGL from "three/examples/jsm/capabilities/WebGL.js" import Button from "@components/Button" import NavButton from "@components/NavButton" -import { ExampleFS } from "@constants/sample" -import { ELEVATION_FORWARD_QUATERNION, STATICS } from "@constants/statics" import { FocusType, ViewType } from "@constants/types" -import { ArchControls } from "@game/archControls" -import { TargetControls } from "@game/targetControls" -import { Tree, TreeNode } from "@game/tree" -import { boundsFromObjects } from "@utility/bounds" -import { createOrthographicCamera, createPerspectiveCamera } from "@utility/camera" -import { createLighting } from "@utility/lighting" -import { createScene } from "@utility/scene" -import { tweenOpacity } from "@utility/tween" +import GameRenderer, { CameraType } from "@game/GameRenderer" // TODO: Cull whole node if it is being partially culled (https://discourse.threejs.org/t/how-to-do-frustum-culling-with-instancedmesh/22633/5). // TODO: Use actual FS data from native client. -const ARCH_CONTROLS_OFFSET = { - [ViewType.ELEVATION]: 4, - [ViewType.PLAN]: 2, -} -const TARGET_CONTROLS_OFFSET = 1 - -const PLAN_MATCH_OPACITY_MAP: Record = { - [-1]: 0.2, - [1]: 0, - [0]: 1, -} - const GameBoard: VoidComponent = () => { - const { code } = useParams() - - const { scene, renderer, cleanup: sceneCleanup } = createScene() - const { ambientLight, directionalLight, cleanup: lightingCleanup } = createLighting() - - const [isCameraPerspective, _setIsCameraPerspective] = createSignal(true) - const camera = isCameraPerspective() ? createPerspectiveCamera() : createOrthographicCamera() - - const archControls = new ArchControls(camera) - const targetControls = new TargetControls([], camera, (mesh) => mesh.name === TreeNode.MESH_NAME) + const code = "WRXCB" - const tweenGroup = new TweenGroup() - - const selfFsTree = new Tree().setFromRawData(ExampleFS, 1) - const opponentFsTree = new Tree().setFromRawData(ExampleFS, 2) - - const raycaster = new Raycaster() - let lastHoveredNode: TreeNode | undefined + const [cameraType, setCameraType] = createSignal(CameraType.PERSPECTIVE) const [view, setView] = createSignal(ViewType.ELEVATION) const [focus, setFocus] = createSignal(FocusType.NONE) - const getFocussedTree = () => - (focus() === FocusType.SELF && selfFsTree) || (focus() === FocusType.OPPONENT && opponentFsTree) || undefined - - const disableContextMenu = (event: MouseEvent) => { - event.preventDefault() - } - - const handleNodeHover = (event: MouseEvent) => { - raycaster.setFromCamera( - new Vector2((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1), - camera, - ) - - const targetTree = getFocussedTree() - if (!targetTree) { - return - } - - const intersects = raycaster.intersectObjects([targetTree], true) - - const [matched] = intersects - .map((intersection) => intersection.object) - .filter((mesh) => mesh.name === TreeNode.MESH_NAME) - .map((obj) => obj as unknown as TreeNode) - - if (!matched) { - if (lastHoveredNode) { - lastHoveredNode.glow(false, tweenGroup) - lastHoveredNode = undefined - } - return - } - - if (matched !== lastHoveredNode) { - if (lastHoveredNode) { - lastHoveredNode.glow(false, tweenGroup) - } - lastHoveredNode = matched - } - - matched.glow(true, tweenGroup) - } - - const draw = (time: number = 0) => { - archControls.update(time) - targetControls.update(time) - tweenGroup.update(time) - renderer.render(scene, camera) - } - - onMount(() => { - if (!WebGL.isWebGL2Available()) { - toast.error(`WebGL is not available ${WebGL.getWebGL2ErrorMessage()}`, { duration: -1 }) - return - } - - // Renderer - renderer.setPixelRatio(window.devicePixelRatio) - renderer.setSize(window.innerWidth, window.innerHeight) - renderer.setAnimationLoop(draw) - - // Lighting - directionalLight.position.copy(STATICS.DIRECTIONAL_LIGHT.position) - scene.add(ambientLight) - scene.add(directionalLight) - - // Node trees - selfFsTree.position.copy(STATICS.SELF.position) - selfFsTree.quaternion.copy(STATICS.SELF.rotation) - opponentFsTree.position.copy(STATICS.OPPONENT.position) - opponentFsTree.quaternion.copy(STATICS.OPPONENT.rotation) - scene.add(selfFsTree) - scene.add(opponentFsTree) - selfFsTree.recomputeBounds() - opponentFsTree.recomputeBounds() - - // Controls - archControls.cameraOffset = ARCH_CONTROLS_OFFSET[view()] - targetControls.cameraOffset = TARGET_CONTROLS_OFFSET - archControls.addEventListener("drill", (event) => - getFocussedTree()?.traverseLevelOrder((mesh, levelIdx) => - tweenOpacity( - tweenGroup, - mesh, - event.historyIdx === -1 ? 1 : PLAN_MATCH_OPACITY_MAP[Math.sign(event.historyIdx - levelIdx)], - ), - ), - ) - targetControls.addEventListener("select", (event) => { - tweenOpacity(event.tweenGroup, event.intersect as unknown as TreeNode, 1) - }) - - // Node Hover - window.addEventListener("mousemove", handleNodeHover) - - // Disable context menu - window.addEventListener("contextmenu", disableContextMenu) - - // Resize handler - window.addEventListener("resize", () => { - renderer.setSize(window.innerWidth, window.innerHeight) - }) - }) - - // Handle tree views createEffect(() => { - // Set position and rotation for NONE focus. if (focus() === FocusType.NONE) { setView(ViewType.ELEVATION) - selfFsTree.traverseLevelOrder((node) => tweenOpacity(tweenGroup, node, 1)) - opponentFsTree.traverseLevelOrder((node) => tweenOpacity(tweenGroup, node, 1)) - return } - - switch (view()) { - case ViewType.ELEVATION: { - getFocussedTree()?.traverseLevelOrder((node) => tweenOpacity(tweenGroup, node, 1)) - return - } - case ViewType.PLAN: { - getFocussedTree()?.traverseLevelOrder((node, levelIdx) => - tweenOpacity(tweenGroup, node, PLAN_MATCH_OPACITY_MAP[Math.sign(0 - levelIdx)]), - ) - return - } - } - }) - - // Handle arch controls - createEffect(() => { - if (focus() === FocusType.NONE) { - archControls.cameraOffset = ARCH_CONTROLS_OFFSET[ViewType.ELEVATION] - archControls.setPoses([boundsFromObjects(selfFsTree, opponentFsTree)], ELEVATION_FORWARD_QUATERNION) - return - } - - archControls.cameraOffset = ARCH_CONTROLS_OFFSET[view()] - - const targetTree = getFocussedTree() - if (!targetTree) { - return - } - - // TODO: Preserve history for PLAN view when switching views. - switch (view()) { - case ViewType.ELEVATION: { - archControls.setPoses([boundsFromObjects(targetTree)], ELEVATION_FORWARD_QUATERNION) - return - } - case ViewType.PLAN: { - archControls.setPoses(targetTree.levelBounds, targetTree.quaternion.clone()) - return - } - } - }) - - // Handle target controls - createEffect(() => { - const targetTree = getFocussedTree() - targetControls.setTargets(targetTree ? [targetTree] : []) - targetControls.enabled = focus() !== FocusType.NONE - }) - - onCleanup(() => { - selfFsTree.clear() - opponentFsTree.clear() - camera.clear() - lightingCleanup() - sceneCleanup() - - window.removeEventListener("contextmenu", disableContextMenu) }) return ( <> - {renderer.domElement} + toast.error(renderableError, { duration: -1 })} + focus={focus()} + view={view()} + />
diff --git a/pkg/client/frontend/src/solid-three.d.ts b/pkg/client/frontend/src/solid-three.d.ts new file mode 100644 index 0000000..b33f71d --- /dev/null +++ b/pkg/client/frontend/src/solid-three.d.ts @@ -0,0 +1,27 @@ +import { Camera, EventHandlers, Node, Object3DNode, Overwrite, Vector3 } from "solid-three" +import { Controls, Event, Line3 } from "three" + +export type Line3Props = Overwrite< + Node, + { + position?: Vector3 + up?: Vector3 + scale?: Vector3 + rotation?: Euler + matrix?: Matrix4 + quaternion?: Quaternion + layers?: Layers + dispose?: (() => void) | null + geometry?: JSX.Element | BufferGeometry | null + material?: JSX.Element | Material | null + } +> & + EventHandlers + +declare module "solid-js" { + namespace JSX { + interface IntrinsicElements { + line3: Line3Props + } + } +} diff --git a/pkg/client/frontend/src/utility/bounds.ts b/pkg/client/frontend/src/utility/bounds.ts index 2a9b578..bba6195 100644 --- a/pkg/client/frontend/src/utility/bounds.ts +++ b/pkg/client/frontend/src/utility/bounds.ts @@ -1,17 +1,13 @@ -import { Box3, Object3D, Vector3 } from "three" - -const _bounds = new Box3() -const _boundCenter = new Vector3() -const _boundSize = new Vector3() +import { Box3, Object3D, Vector3, Vector3Tuple } from "three" export const boundsFromObjects = (...objects: Object3D[]) => { - _bounds.makeEmpty() - objects.forEach((obj) => _bounds.expandByObject(obj)) - return _bounds.clone() + const bounds = new Box3() + objects.forEach((obj) => bounds.expandByObject(obj)) + return bounds.clone() } -export const unpackBounds = (bounds: Box3) => { - bounds.getCenter(_boundCenter) - bounds.getSize(_boundSize) - return [_boundCenter.clone(), _boundSize.clone()] as [Vector3, Vector3] +export const boundsFromPoints = (...points: Vector3Tuple[]) => { + const bounds = new Box3() + bounds.setFromPoints(points.map((point) => new Vector3(...point))) + return bounds.clone() } diff --git a/pkg/client/frontend/src/utility/camera.ts b/pkg/client/frontend/src/utility/camera.ts index 2b182a3..63d4f9a 100644 --- a/pkg/client/frontend/src/utility/camera.ts +++ b/pkg/client/frontend/src/utility/camera.ts @@ -1,31 +1,4 @@ -import { Camera, OrthographicCamera, PerspectiveCamera, Quaternion } from "three" - -const ORTHO_FRUSTUM_SIZE = 10 -const PERSPECTIVE_FOV = 70 -const NEAR = 0.1 -const FAR = 2000 - -export const createPerspectiveCamera = (fov = PERSPECTIVE_FOV) => { - const aspect = window.innerWidth / window.innerHeight - return new PerspectiveCamera(fov, aspect, NEAR, FAR) -} - -export const createOrthographicCamera = (frustumSize = ORTHO_FRUSTUM_SIZE) => { - const aspect = window.innerWidth / window.innerHeight - const left = (-frustumSize * aspect) / 2 - const right = (frustumSize * aspect) / 2 - const top = frustumSize / 2 - const bottom = -frustumSize / 2 - return new OrthographicCamera(left, right, top, bottom, NEAR, FAR) -} - -export const isPerspectiveCamera = (cam: OrthographicCamera | PerspectiveCamera): cam is PerspectiveCamera => { - return "isPerspectiveCamera" in cam && cam.isPerspectiveCamera -} - -export const isOrthographicCamera = (cam: OrthographicCamera | PerspectiveCamera): cam is OrthographicCamera => { - return "isOrthographicCamera" in cam && cam.isOrthographicCamera -} +import { Camera, Quaternion } from "three" export const lookAtFromQuaternion = (cam: Camera, q: Quaternion) => q.clone().multiply(new Quaternion().setFromAxisAngle(cam.up, Math.PI)).normalize() diff --git a/pkg/client/frontend/src/utility/lighting.ts b/pkg/client/frontend/src/utility/lighting.ts deleted file mode 100644 index 4a46e85..0000000 --- a/pkg/client/frontend/src/utility/lighting.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { AmbientLight, DirectionalLight } from "three" - -export const createLighting = () => { - const ambientLight = new AmbientLight(0x193751, 2) - const directionalLight = new DirectionalLight(0xffffff, 2) - - const cleanup = () => { - directionalLight.dispose() - ambientLight.dispose() - } - - return { - ambientLight, - directionalLight, - cleanup, - } -} diff --git a/pkg/client/frontend/src/utility/pureTransform.ts b/pkg/client/frontend/src/utility/pureTransform.ts index 4a0b32a..dfe9af7 100644 --- a/pkg/client/frontend/src/utility/pureTransform.ts +++ b/pkg/client/frontend/src/utility/pureTransform.ts @@ -1,11 +1,11 @@ import { Object3D, Quaternion, Vector3 } from "three" -const _position = new Vector3() -const _rotation = new Quaternion() - export const getWorldPose = (object: Object3D) => { - object.getWorldPosition(_position) - object.getWorldQuaternion(_rotation) + const position = new Vector3() + const rotation = new Quaternion() + + object.getWorldPosition(position) + object.getWorldQuaternion(rotation) - return [_position.clone(), _rotation.clone()] as [Vector3, Quaternion] + return [position, rotation] as [Vector3, Quaternion] } diff --git a/pkg/client/frontend/src/utility/quaternion.ts b/pkg/client/frontend/src/utility/quaternion.ts index 500099d..9569701 100644 --- a/pkg/client/frontend/src/utility/quaternion.ts +++ b/pkg/client/frontend/src/utility/quaternion.ts @@ -1,7 +1,10 @@ import { Quaternion } from "three" export const averageQuaternions = (...inputs: Quaternion[]) => { - const rotation = inputs[0].clone() + if (!inputs.length) { + return new Quaternion() + } + const rotation = inputs[0]!.clone() inputs.slice(1).forEach((input, idx) => { rotation.slerp(input, 1 / (idx + 1)) }) diff --git a/pkg/client/frontend/src/utility/scene.ts b/pkg/client/frontend/src/utility/scene.ts deleted file mode 100644 index ba70ac9..0000000 --- a/pkg/client/frontend/src/utility/scene.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Scene, WebGLRenderer } from "three" - -export const createScene = () => { - const scene = new Scene() - const renderer = new WebGLRenderer({ antialias: true, alpha: true }) - - const cleanup = () => { - renderer.dispose() - scene.clear() - } - - return { - scene, - renderer, - cleanup, - } -} diff --git a/pkg/client/frontend/src/utility/tween.ts b/pkg/client/frontend/src/utility/tween.ts index adad9cc..3b9049a 100644 --- a/pkg/client/frontend/src/utility/tween.ts +++ b/pkg/client/frontend/src/utility/tween.ts @@ -9,34 +9,6 @@ type TweenOptions = { const DEFAULT_TWEEN_OPTIONS = { timing: 400 } -export const tweenTransform = ( - group: TweenGroup, - object: Object3D, - to: TweenTransform, - tweenOptions: Partial = DEFAULT_TWEEN_OPTIONS, -) => { - if (!group) { - return - } - - const qFrom = object.quaternion.clone() - const qTo = to.rotation.normalize() - - group.add( - new Tween({ position: object.position.clone(), time: 0 }) - .to({ position: to.position, time: 1 }, tweenOptions.timing ?? DEFAULT_TWEEN_OPTIONS.timing) - .easing(Easing.Cubic.InOut) - .onUpdate(({ position, time }) => { - object.position.copy(position) - object.quaternion.slerpQuaternions(qFrom, qTo, time) - }) - .onComplete(() => { - tweenOptions.onComplete?.() - }) - .start(), - ) -} - export const tweenOpacity = ( group: TweenGroup, object: Mesh | Line, diff --git a/pkg/client/frontend/tsconfig.json b/pkg/client/frontend/tsconfig.json index 77fd1c5..ea43323 100644 --- a/pkg/client/frontend/tsconfig.json +++ b/pkg/client/frontend/tsconfig.json @@ -1,5 +1,21 @@ { "compilerOptions": { + /* Build properties */ + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "noEmit": true, + "isolatedModules": true, + "allowImportingTsExtensions": true, + "allowSyntheticDefaultImports": true, + + /* Solid configuration */ + "jsx": "preserve", + "jsxImportSource": "solid-js", + "types": ["vite/client"], + + /* Path resolution */ "baseUrl": ".", "paths": { "@animations/*": ["./src/animations/*"], @@ -11,25 +27,35 @@ "@pages/*": ["./src/pages/*"], "@shaders/*": ["./src/shaders/*"], "@utility/*": ["./src/utility/*"], - "@wails/*": ["./wailsjs/*"] + "@wails/*": ["./wailsjs/*"], + "solid-three": ["node_modules/solid-three/types/index.d.ts"] }, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ESNext", - "useDefineForClassFields": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "esModuleInterop": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", + + /* Linting */ "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - "types": ["vite/client"] - } + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "useUnknownInCatchVariables": true, + "noPropertyAccessFromIndexSignature": true, + // "verbatimModuleSyntax": true, // Enable later + + /* DX */ + "sourceMap": true, + "incremental": true, + "noErrorTruncation": true, + "pretty": true, + "skipLibCheck": true + }, + "include": ["./src/**/*"] } diff --git a/pkg/client/frontend/vite.config.ts b/pkg/client/frontend/vite.config.ts index bc6ce36..9e3d5d2 100644 --- a/pkg/client/frontend/vite.config.ts +++ b/pkg/client/frontend/vite.config.ts @@ -3,11 +3,23 @@ import { defineConfig } from "vite" import glsl from "vite-plugin-glsl" import solid from "vite-plugin-solid" +const GAME_DIR_MATCHER = /src\/game\/.*\.[tj]sx?$/ + export default defineConfig({ build: { target: "esnext", }, - plugins: [solid(), glsl()], + plugins: [ + solid({ + include: GAME_DIR_MATCHER, + solid: { moduleName: "solid-three", generate: "universal" }, + }), + solid({ + exclude: GAME_DIR_MATCHER, + solid: { generate: "dom" }, + }), + glsl(), + ], resolve: { alias: { "@animations": path.resolve(__dirname, "./src/animations"),