diff --git a/models/BoxTextured.glb b/models/BoxTextured.glb new file mode 100644 index 0000000..cfd92e7 Binary files /dev/null and b/models/BoxTextured.glb differ diff --git a/models/PineTree.glb b/models/PineTree.glb new file mode 100644 index 0000000..5d2fc30 Binary files /dev/null and b/models/PineTree.glb differ diff --git a/package-lock.json b/package-lock.json index 8ff0893..e560d95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,7 +174,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -524,7 +523,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -568,7 +566,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1081,6 +1078,7 @@ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", @@ -1096,6 +1094,7 @@ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/core": "^0.17.0" }, @@ -1109,6 +1108,7 @@ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -1122,6 +1122,7 @@ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1146,6 +1147,7 @@ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1172,6 +1174,7 @@ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1182,6 +1185,7 @@ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" @@ -1234,6 +1238,7 @@ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18.0" } @@ -1244,6 +1249,7 @@ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" @@ -1258,6 +1264,7 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=12.22" }, @@ -1272,6 +1279,7 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18" }, @@ -1416,7 +1424,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.4.tgz", "integrity": "sha512-cG0C5fMZ1jyW6WCsf4LoHGvaIAJCEVA/ioqKoYRwoSfXkOf+17KupK1OUQyUCw5XoRn+oWA1FulJQOYlXnb9Gw==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/schema": "4.3.4", @@ -3174,7 +3181,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.134.9.tgz", "integrity": "sha512-JIxFamShs3gRIkOxpgz/3bglbSKZHMrzKASwNFg+sQPVXVPOLtN35D5PuEDAFTPPht9Wv48WWUNYE03ZytnNug==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/react-store": "^0.8.0", @@ -3240,7 +3246,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.134.9.tgz", "integrity": "sha512-9Vr8tYC59I70DYGVRknRf4vjQMjSfHvmc+iTM8vcpwERBh3Vgkv90f8ol85KHKqjorSsCqMeYFhFt8AM4A4CSw==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/store": "^0.8.0", @@ -3303,7 +3308,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3697,7 +3701,8 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/node": { "version": "22.19.0", @@ -3705,7 +3710,6 @@ "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3722,7 +3726,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3732,7 +3735,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3783,7 +3785,6 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -3973,7 +3974,6 @@ "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.3", @@ -4428,7 +4428,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4442,6 +4441,7 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -4462,6 +4462,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4489,6 +4490,7 @@ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4504,7 +4506,8 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0" + "license": "Python-2.0", + "peer": true }, "node_modules/aria-hidden": { "version": "1.2.6", @@ -4578,6 +4581,7 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4616,7 +4620,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4647,6 +4650,7 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -4695,6 +4699,7 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4737,6 +4742,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4749,7 +4755,8 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/commander": { "version": "11.1.0", @@ -4776,7 +4783,8 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/confbox": { "version": "0.1.8", @@ -4804,6 +4812,7 @@ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4912,8 +4921,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/d3-color": { "version": "3.1.0", @@ -4972,7 +4980,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -5075,7 +5082,8 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dequal": { "version": "2.0.3", @@ -5294,6 +5302,7 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -5444,7 +5453,6 @@ "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", @@ -5531,6 +5539,7 @@ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5561,6 +5570,7 @@ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -5579,6 +5589,7 @@ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -5592,6 +5603,7 @@ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -5605,6 +5617,7 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -5625,6 +5638,7 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5644,7 +5658,8 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-glob": { "version": "3.3.3", @@ -5681,14 +5696,16 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fastq": { "version": "1.19.1", @@ -5730,6 +5747,7 @@ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -5756,6 +5774,7 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -5773,6 +5792,7 @@ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -5786,7 +5806,8 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -5840,6 +5861,7 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -5888,6 +5910,7 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -5969,6 +5992,7 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -5991,6 +6015,7 @@ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6008,6 +6033,7 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.19" } @@ -6089,7 +6115,8 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/jiti": { "version": "2.6.1", @@ -6113,6 +6140,7 @@ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -6126,7 +6154,6 @@ "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.19", "@asamuzakjp/dom-selector": "^6.7.3", @@ -6179,21 +6206,24 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json5": { "version": "2.2.3", @@ -6214,6 +6244,7 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -6230,6 +6261,7 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -6493,6 +6525,7 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -6508,7 +6541,8 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/loupe": { "version": "3.2.1", @@ -6605,6 +6639,7 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6709,6 +6744,7 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -6727,6 +6763,7 @@ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -6743,6 +6780,7 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -6766,6 +6804,7 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -6798,6 +6837,7 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -6808,6 +6848,7 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -6840,7 +6881,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6894,6 +6934,7 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -6904,7 +6945,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -7058,7 +7098,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7068,7 +7107,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7196,6 +7234,7 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -7334,7 +7373,6 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -7357,6 +7395,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -7370,6 +7409,7 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -7386,7 +7426,6 @@ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", @@ -7438,6 +7477,7 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" }, @@ -7471,6 +7511,7 @@ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7515,8 +7556,7 @@ "version": "4.1.16", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tailwindcss-radix": { "version": "4.0.2", @@ -7569,8 +7609,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -7722,6 +7761,7 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -7735,7 +7775,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7854,6 +7893,7 @@ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -7915,7 +7955,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -8160,6 +8199,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -8193,6 +8233,7 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8248,6 +8289,7 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8261,7 +8303,6 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/components/nodes/scatter-node.tsx b/src/components/nodes/scatter-node.tsx index b3bf84c..826c28b 100644 --- a/src/components/nodes/scatter-node.tsx +++ b/src/components/nodes/scatter-node.tsx @@ -1,8 +1,10 @@ +import { useEffect } from 'react'; import { useReactFlow, type NodeProps } from 'reactflow'; import * as helpers from './helpers'; import * as TerrainGenNode from '@/components/common/terraingen-node'; +import { useGraphGlobals } from '@/hooks/use-graph-globals'; import * as nodeTypes from '@/lib/graph/node-types'; const HANDLES = nodeTypes.HANDLES.scatter; @@ -10,11 +12,16 @@ type ScatterNodeData = nodeTypes.Scatter['data']; function ScatterNode({ id, data, ...props }: NodeProps) { const { setNodes } = useReactFlow(); + const { triggerNodePipelineUpdate } = useGraphGlobals(); const onChange = (key: 'instances' | 'threshold', value: number) => { helpers.updateNodeData({ id, setNodes, newData: { [key]: value } }); }; + useEffect(() => { + triggerNodePipelineUpdate(id); + }, [data.instances, data.threshold, id, triggerNodePipelineUpdate]); + return ( diff --git a/src/lib/graph/index.ts b/src/lib/graph/index.ts index b643925..555484b 100644 --- a/src/lib/graph/index.ts +++ b/src/lib/graph/index.ts @@ -156,6 +156,12 @@ function generatePipelines( return { displacePipeline }; } + const maskEdge = edges.find( + (edge) => + edge.target === scatterNode.id && edge.targetHandle === nodeTypes.HANDLES.scatter.in.a, + ); + + const maskSourceNode = orderedDependencyNodes.find((node) => node.id === maskEdge?.source); let transformConfig: { translate: string; rotate: string; scale: string } | undefined; if (transformNode) { const translateEdge = edges.find( @@ -203,6 +209,14 @@ function generatePipelines( outgoingHandleId: scatterEdge.sourceHandle!, }), meshPath: geometryNode.data.meshPath, + maskKey: + maskSourceNode && maskEdge + ? nodeMapping.getHandleKey({ + sourceNode: maskSourceNode, + outgoingHandleId: maskEdge.sourceHandle!, + }) + : undefined, + threshold: scatterNode.data.threshold, transform: transformConfig, fileContent: geometryNode.type === 'loadGeo' ? (geometryNode.data.fileContent as string) : undefined, diff --git a/src/lib/renderers/pipelines/instance-points-pipeline.ts b/src/lib/renderers/pipelines/instance-points-pipeline.ts index bad98eb..3290bff 100644 --- a/src/lib/renderers/pipelines/instance-points-pipeline.ts +++ b/src/lib/renderers/pipelines/instance-points-pipeline.ts @@ -10,6 +10,7 @@ export class InstancePointsPipeline { instanceCount = 1; instanceCountBuffer: GPUBuffer; + //numCreatedInstancesBuffer: GPUBuffer; instancingBindGroupLayout: GPUBindGroupLayout; instancingBindGroup: GPUBindGroup; @@ -34,7 +35,7 @@ export class InstancePointsPipeline { this.customInstanceCode = customInstanceCode; this.instancePoints = this.device.createBuffer({ label: 'instancing points vertex buffer', - size: Math.max(this.instanceCount * 96, 96), + size: Math.max(this.instanceCount * 112, 112), usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | @@ -52,6 +53,12 @@ export class InstancePointsPipeline { }); this.device.queue.writeBuffer(this.instanceCountBuffer, 0, numInstances); + // this.numCreatedInstancesBuffer = device.createBuffer({ + // label: "buffer for how many valid points have been created", + // size: 4, + // usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + // }); + this.instancingBindGroupLayout = this.device.createBindGroupLayout({ label: 'instancing bind group layout', entries: [ @@ -64,12 +71,21 @@ export class InstancePointsPipeline { }, }, { + // number of instances binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform', }, }, + // { + // // count how many instances we made + // binding: 2, + // visibility: GPUShaderStage.COMPUTE, + // buffer: { + // type: 'storage', + // }, + // }, ], }); diff --git a/src/lib/renderers/pipelines/instancer.ts b/src/lib/renderers/pipelines/instancer.ts index 72946be..ed66681 100644 --- a/src/lib/renderers/pipelines/instancer.ts +++ b/src/lib/renderers/pipelines/instancer.ts @@ -13,6 +13,9 @@ export class IndirectInstancer { instancePointsComputePipeline: InstancePointsPipeline; + textureBindGroup: GPUBindGroup; + textureArray: GPUTexture; + transformBuffer: GPUBuffer | undefined; transformBindGroupLayout: GPUBindGroupLayout | undefined; transformBindGroup: GPUBindGroup | undefined; @@ -24,6 +27,7 @@ export class IndirectInstancer { instanceIndexBuffer: GPUBuffer, sceneUniformsBindGroupLayout: GPUBindGroupLayout, webGPU: WebGPUContext, + imageBitmaps?: ImageBitmap[], transformMatrix?: Float32Array, ) { this.device = device; @@ -135,10 +139,33 @@ export class IndirectInstancer { ], }); + const textureBindGroupLayout = this.device.createBindGroupLayout({ + label: 'texture bind group layout', + entries: [ + { + // sampler type... + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + sampler: { type: 'filtering' }, + }, + { + // texture view + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + texture: { + sampleType: 'float', + viewDimension: '2d-array', + }, + }, + ], + }); + const bindGroupLayouts = [ sceneUniformsBindGroupLayout, this.instancingPointsBindGroupLayout, + textureBindGroupLayout, ]; + if (this.transformBindGroupLayout) { bindGroupLayouts.push(this.transformBindGroupLayout); } @@ -171,18 +198,85 @@ export class IndirectInstancer { targets: [ { format: webGPU.canvasFormat, + blend: { + color: { + srcFactor: 'one', + dstFactor: 'one-minus-src-alpha', + }, + alpha: { + srcFactor: 'one', + dstFactor: 'one-minus-src-alpha', + }, + }, }, ], }, }); + + // create buffers for the image bitmaps + const firstSource = imageBitmaps![0]; + this.textureArray = this.device.createTexture({ + label: 'FAT FUCKING TEXTURE!!!!', + format: 'rgba8unorm', + size: { + width: firstSource.width, + height: firstSource.height, + depthOrArrayLayers: 20, + }, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + + for (let i = 0; i < imageBitmaps!.length; i++) { + const source = imageBitmaps![i]; + + this.device.queue.copyExternalImageToTexture( + { source: source, flipY: true }, + { + texture: this.textureArray, + premultipliedAlpha: true, + origin: { x: 0, y: 0, z: i + 1 }, + }, + { width: source.width, height: source.height }, + ); + } + + const sampler = this.device.createSampler({ + addressModeU: 'repeat', + addressModeV: 'repeat', + magFilter: 'linear', + minFilter: 'linear', + mipmapFilter: 'linear', + }); + + this.textureBindGroup = this.device.createBindGroup({ + label: 'texture bind group', + layout: textureBindGroupLayout, + entries: [ + { + binding: 0, + resource: sampler, + }, + { + binding: 1, + resource: this.textureArray.createView({ + dimension: '2d-array', + }), + }, + ], + }); } runRenderPass(renderPass: GPURenderPassEncoder, sceneUniforms: GPUBindGroup) { renderPass.setPipeline(this.instancingRenderPipeline); renderPass.setBindGroup(0, sceneUniforms); renderPass.setBindGroup(1, this.instancingPointsBindGroup); + renderPass.setBindGroup(2, this.textureBindGroup); + if (this.transformBindGroup) { - renderPass.setBindGroup(2, this.transformBindGroup); + renderPass.setBindGroup(3, this.transformBindGroup); } renderPass.drawIndirect(this.indirectInstanceBuffer, 0); } diff --git a/src/lib/renderers/terrain-renderer.ts b/src/lib/renderers/terrain-renderer.ts index 66f036d..54659c3 100644 --- a/src/lib/renderers/terrain-renderer.ts +++ b/src/lib/renderers/terrain-renderer.ts @@ -452,7 +452,7 @@ export class TerrainRenderer implements IRenderer { colorAttachments: [ { view: canvasTextureView, - clearValue: [0.3, 0, 0, 1], + clearValue: [0.0, 0, 0, 0], loadOp: 'clear', storeOp: 'store', }, @@ -615,10 +615,10 @@ export class TerrainRenderer implements IRenderer { if (config.outputs.fileContent) { if (config.outputs.fileType === 'obj') { - mesh.parseObjContent(config.outputs.fileContent); + await mesh.parseObjContent(config.outputs.fileContent); } else if (config.outputs.fileType === 'gltf' || config.outputs.fileType === 'glb') { const { gltfWithBuffers, gltf } = await mesh.loadGltf(config.outputs.fileContent); - mesh.parseGLTFContent(gltfWithBuffers, gltf); + await mesh.parseGLTFContent(gltfWithBuffers, gltf); } } else { await mesh.loadObj(path.join(import.meta.env.BASE_URL, config.outputs.meshPath)); @@ -641,13 +641,7 @@ export class TerrainRenderer implements IRenderer { }); this.device.queue.writeBuffer(instanceIndexBuffer, 0, mesh.indices); - const customInstanceShader = jit.generateInstanceShaderCode( - config, - instanceComputeShaderTemplate, - ); - - console.log('custom instance shader:', customInstanceShader); - + // set uniforms this.nodeGraphUniformConfig = config.uniforms; const { totalSize, offsets } = jit.calculateUniformLayout(config.uniforms); this.nodeGraphUniformLayout = offsets; @@ -686,7 +680,16 @@ export class TerrainRenderer implements IRenderer { ], }); - console.log('num instances:', config.outputs.instanceCount); + // create custom instancing shader + if (!config.outputs.maskKey) { + config.outputs.maskKey = 'terrainPos.y'; + } + const customInstanceShader = jit.generateInstanceShaderCode( + config, + instanceComputeShaderTemplate, + ); + + console.log('custom instance shader:', customInstanceShader); // Run compute to create a buffer of points this.instancePointsComputePipeline = new InstancePointsPipeline( @@ -694,6 +697,7 @@ export class TerrainRenderer implements IRenderer { this.stage.groundPlane, this.normalsComputePipeline, config.outputs.instanceCount, + customInstanceShader, ); const encoder = this.device.createCommandEncoder(); @@ -729,6 +733,7 @@ export class TerrainRenderer implements IRenderer { instanceIndexBuffer, this.sceneUniformsBindGroupLayout, this.webGPU, + mesh.textures, transformMatrix, ); } diff --git a/src/lib/scene/mesh.ts b/src/lib/scene/mesh.ts index 21464a1..ce02d5b 100644 --- a/src/lib/scene/mesh.ts +++ b/src/lib/scene/mesh.ts @@ -31,6 +31,7 @@ export abstract class Mesh { vertexBuffer: GPUBuffer | undefined; indexBuffer: GPUBuffer | undefined; + textureBuffer: GPUBuffer[] | undefined; indirectBuffer: GPUBuffer | undefined; @@ -124,6 +125,7 @@ export class OBJ extends Mesh { vertices: Float32Array | undefined; indices: Uint32Array | undefined; + textures: ImageBitmap[] | undefined; constructor() { super(1, 1); @@ -132,10 +134,10 @@ export class OBJ extends Mesh { async loadObj(url: string) { const response = await fetch(url); const text = await response.text(); - this.parseObjContent(text); + await this.parseObjContent(text); } - parseObjContent(text: string) { + async parseObjContent(text: string) { const positions: number[][] = []; const normals: number[][] = []; const uvs: number[][] = []; @@ -174,7 +176,7 @@ export class OBJ extends Mesh { const uv = t >= 0 && t < uvs.length ? uvs[t] : [0, 0]; const nor = n >= 0 && n < normals.length ? normals[n] : [0, 0, 0]; - finalVertices.push(pos[0], pos[1], pos[2], nor[0], nor[1], nor[2], uv[0], uv[1]); + finalVertices.push(pos[0], pos[1], pos[2], nor[0], nor[1], nor[2], uv[0], uv[1], 1); vertexMap.set(key, finalVertices.length / 8 - 1); } @@ -190,8 +192,28 @@ export class OBJ extends Mesh { } } + // set grey texture for obj by default + const baseColor: number[] = [0.5, 0.5, 0.5, 1.0]; + const [r, g, b, a] = baseColor.map((v) => Math.round(v * 255)); + + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = `rgba(${r},${g},${b},${a})`; + ctx.fillRect(0, 0, 1, 1); + + const blob = await new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (blob) resolve(blob); + else reject(new Error('Canvas toBlob() returned null')); + }, 'image/png'); + }); + const imageBitmap = await createImageBitmap(blob); + this.vertices = new Float32Array(finalVertices); this.indices = new Uint32Array(finalIndices); + this.textures = [imageBitmap]; } async loadGltf(url: string): Promise<{ gltfWithBuffers: GLTFWithBuffers; gltf: GLTF }> { @@ -204,9 +226,10 @@ export class OBJ extends Mesh { }; } - parseGLTFContent(gltfWithBuffers: GLTFWithBuffers, gltf: GLTF) { + async parseGLTFContent(gltfWithBuffers: GLTFWithBuffers, gltf: GLTF) { const finalVertices: number[] = []; const finalIndices: number[] = []; + const finalBitmaps: ImageBitmap[] = []; for (const mesh of gltf.meshes!) { for (const prim of mesh.primitives) { @@ -240,21 +263,26 @@ export class OBJ extends Mesh { // load uvs let uvs: Float32Array; - if (prim.attributes['TEXCOORD_0']) { - const uvAccessor = gltf.accessors![prim.attributes['TEXCOORD_0']]; + if (prim.attributes.TEXCOORD_0 !== undefined) { + const uvAccessorIndex = prim.attributes.TEXCOORD_0; + const uvAccessor = gltf.accessors![uvAccessorIndex]; const uvBufferView = gltf.bufferViews![uvAccessor.bufferView!]; const uvBuffer = gltfWithBuffers.buffers[uvBufferView.buffer]; const uvByteOffset = - (uvBufferView.byteOffset ?? 0) + - (uvAccessor.byteOffset ?? 0) + - norBuffer.byteOffset; - const uvArrayLength = uvAccessor.count * numComponents[uvAccessor.type]; + (uvBufferView.byteOffset ?? 0) + (uvAccessor.byteOffset ?? 0) + uvBuffer.byteOffset; + const uvArrayLength = uvAccessor.count * 2; uvs = new Float32Array(uvBuffer.arrayBuffer, uvByteOffset, uvArrayLength); } else { const tempUVs: number[] = []; for (let i = 0; i < positions.length / 3; i++) { - tempUVs.push(0, 1); + if (i % 3 == 0) { + tempUVs.push(0, 1); + } else if (i % 3 == 1) { + tempUVs.push(1, 1); + } else { + tempUVs.push(0, 0); + } } uvs = new Float32Array(tempUVs); } @@ -284,6 +312,10 @@ export class OBJ extends Mesh { break; } + const vertexOffset = finalVertices.length / 9; + + const materialIndex = prim.material ?? 0; + // push these guys into the final array for (let i = 0; i < positions.length / 3; i++) { finalVertices.push( @@ -295,15 +327,76 @@ export class OBJ extends Mesh { normals[3 * i + 2], uvs[2 * i], uvs[2 * i + 1], + materialIndex + 1, ); } + for (let i = 0; i < idxArray!.length; i++) { - finalIndices.push(idxArray![i]); + finalIndices.push(idxArray![i] + vertexOffset); + } + } + } + + // load textures here... + for (const gltfMaterial of gltf.materials!) { + // base color + if (gltfMaterial.pbrMetallicRoughness?.baseColorTexture) { + const texInfo = gltfMaterial.pbrMetallicRoughness?.baseColorTexture; + const gltfTexture = gltf.textures![texInfo.index]; + + const imageIndex = gltfTexture.source!; + const imageDef = gltf.images![imageIndex]; + + const view = gltf.bufferViews![imageDef.bufferView!]; + const buffer = gltfWithBuffers.buffers[view.buffer]; + + const byteOffset = (view.byteOffset ?? 0) + buffer.byteOffset; + const byteLength = view.byteLength; + + const imageBytes = new Uint8Array(buffer.arrayBuffer, byteOffset, byteLength); + + // create image (sus) + const imageBitmap = await createImageBitmap( + new Blob([imageBytes], { type: imageDef.mimeType ?? 'image/png' }), + ); + + finalBitmaps.push(imageBitmap); + } else { + // if there is no texture for base color, create a base color bitmap + let baseColor: number[] = [0.5, 0.5, 0.5, 1.0]; + + if ( + gltfMaterial.pbrMetallicRoughness && + gltfMaterial.pbrMetallicRoughness.baseColorFactor + ) { + baseColor = gltfMaterial.pbrMetallicRoughness.baseColorFactor; } + + const [r, g, b, a] = baseColor.map((v) => Math.round(v * 255)); + + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = `rgba(${r},${g},${b},${a})`; + ctx.fillRect(0, 0, 1, 1); + + const blob = await new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (blob) resolve(blob); + else reject(new Error('Canvas toBlob() returned null')); + }, 'image/png'); + }); + const imageBitmap = await createImageBitmap(blob); + + finalBitmaps.push(imageBitmap); } } this.vertices = new Float32Array(finalVertices); this.indices = new Uint32Array(finalIndices); + this.textures = finalBitmaps; + + console.log('textures', finalBitmaps); } } diff --git a/src/lib/shaders/common.wgsl b/src/lib/shaders/common.wgsl index 3500329..aa97cd7 100644 --- a/src/lib/shaders/common.wgsl +++ b/src/lib/shaders/common.wgsl @@ -32,4 +32,5 @@ struct InstanceVertex { nor: vec3, uv: vec2, rotMat: mat3x3, + used: u32, }; diff --git a/src/lib/shaders/instance_points.cs.wgsl b/src/lib/shaders/instance_points.cs.wgsl index 70bb76f..7dadcb3 100644 --- a/src/lib/shaders/instance_points.cs.wgsl +++ b/src/lib/shaders/instance_points.cs.wgsl @@ -95,4 +95,5 @@ fn main(@builtin(global_invocation_id) id: vec3) { instance_pts[id.x].nor = vec3(normal.x, normal.y, normal.z); instance_pts[id.x].uv = vec2(0.0, 0.0); instance_pts[id.x].rotMat = rot; + instance_pts[id.x].used = 1u; } diff --git a/src/lib/shaders/instancing.wgsl b/src/lib/shaders/instancing.wgsl index ffdb5a3..f26604b 100644 --- a/src/lib/shaders/instancing.wgsl +++ b/src/lib/shaders/instancing.wgsl @@ -1,16 +1,23 @@ @group(0) @binding(0) var camera : CameraUniforms; +// add another uniform for whether or not texture is being used + @group(1) @binding(0) -var instance_pts: array; // 8 floats per instance- pos, nor, uv +var instance_pts: array; @group(1) @binding(1) -var vertices: array; // 8 floats per vertex +var vertices: array; // 8 floats + 1 u32 per vertex @group(1) @binding(2) var indices: array; -@group(2) @binding(0) +// insert binding for array of image textures +@group(2) @binding(0) var ourSampler: sampler; +@group(2) @binding(1) var ourTexture: texture_2d_array; + +@group(3) @binding(0) + var transform_matrix: mat4x4; struct VertexIn { @@ -23,13 +30,15 @@ struct VertexOut { @location(0) pos : vec3f, @location(1) nor : vec3f, @location(2) uv : vec2f, + @location(3) @interpolate(flat) tex_id: u32, + @location(4) @interpolate(flat) used: u32, }; @vertex fn vs_main(in : VertexIn) -> VertexOut { var out : VertexOut; - let vOffset = in.instance_index * 8u; + let vOffset = in.instance_index * 9u; // get point position var pos = instance_pts[in.instance_index].pos; @@ -37,8 +46,10 @@ fn vs_main(in : VertexIn) -> VertexOut { // point nor for testing... var nor = normalize(instance_pts[in.instance_index].nor); + var used = instance_pts[in.instance_index].used; + let idx = indices[in.vertex_index]; - let base = idx * 8u; + let base = idx * 9u; let local = vec3f( vertices[base + 0], vertices[base + 1], @@ -49,15 +60,13 @@ fn vs_main(in : VertexIn) -> VertexOut { vertices[base + 4], vertices[base + 5], ); + let vert_uv = vec2f(vertices[base + 6], vertices[base + 7]); + let texture_id = u32(vertices[base + 8]); // do transformations let rot = instance_pts[in.instance_index].rotMat; - - let transformed_local = (transform_matrix * vec4f(local, 1.0)).xyz; - - let rotated = rot * transformed_local; // apply orientation - let world = vec4(pos + rotated, 1.0); - //let world = vec4(pos + local, 1.0); + let rotated = rot * local; + let world = transform_matrix * vec4(pos + rotated, 1.0); let world_pos = camera.viewProjMat * world; // transform normals too @@ -66,35 +75,51 @@ fn vs_main(in : VertexIn) -> VertexOut { transform_matrix[1].xyz, transform_matrix[2].xyz ); - let transformed_nor = normal_matrix * vert_nor; - let new_nor = rot * transformed_nor; + let transformed_nor = rot * vert_nor; + let new_nor = normalize(normal_matrix * transformed_nor); // set output out.position = world_pos; out.pos = world_pos.xyz; out.nor = normalize(new_nor); - out.uv = vec2f(1.0, 1.0); + out.uv = vert_uv; + out.tex_id = texture_id; + out.used = used; return out; } @fragment fn fs_main(in: VertexOut) -> @location(0) vec4f { + if (in.used == 0u) { + discard; + } + // do lambertian shading let lightDir = normalize(vec3f(-1.0, 1.0, -1.0)); let diffuse = max(dot(in.nor, lightDir), 0.0); - var color = vec3f(0.0, 0.0, 0.0); - - if (diffuse > 0.75) { - color = vec3f(0.58, 1.0, 0.235); - } else if (diffuse > 0.5) { - color = vec3f(0.447, 0.749, 0.313); - } else if (diffuse > 0.25) { - color = vec3f(0.309, 0.490, 0.396); - } else { - color = vec3f(0.176, 0.235, 0.478); - } + let texcoord = vec2f(in.uv.x, 1.0 - in.uv.y); + let color = textureSample(ourTexture, ourSampler, texcoord, in.tex_id); + //let color = vec4(in.uv.x, in.uv.y, 0.0, 1.0); - return vec4f(color, 1.0); + if (color.a < 0.5) { + discard; +} + + return color; + + // var color = vec3f(0.0, 0.0, 0.0); + + // if (diffuse > 0.75) { + // color = vec3f(0.58, 1.0, 0.235); + // } else if (diffuse > 0.5) { + // color = vec3f(0.447, 0.749, 0.313); + // } else if (diffuse > 0.25) { + // color = vec3f(0.309, 0.490, 0.396); + // } else { + // color = vec3f(0.176, 0.235, 0.478); + // } + + // return vec4f(color, 1.0); } diff --git a/src/lib/shaders/jit/index.ts b/src/lib/shaders/jit/index.ts index 0f0ceb2..f60d660 100644 --- a/src/lib/shaders/jit/index.ts +++ b/src/lib/shaders/jit/index.ts @@ -41,8 +41,10 @@ export function generateInstanceShaderCode( .join('\n'); const posKey = shaderConfig.outputs.instancePositions; + const maskKey = shaderConfig.outputs.maskKey; + const threshold = shaderConfig.outputs.threshold; - return template.content({ uniforms, utils, body, posKey }); + return template.content({ uniforms, utils, body, posKey, maskKey, threshold }); } // double check this diff --git a/src/lib/shaders/jit/templates/instance.compute.ts b/src/lib/shaders/jit/templates/instance.compute.ts index 52375dc..48c6964 100644 --- a/src/lib/shaders/jit/templates/instance.compute.ts +++ b/src/lib/shaders/jit/templates/instance.compute.ts @@ -2,15 +2,19 @@ import commonShaderContent from '@/lib/shaders/common.wgsl?raw'; import * as shaders from '@/lib/shaders/jit/types/shaders'; export const instanceComputeShaderTemplate: shaders.InstancingShaderTemplate = { - content: ({ uniforms, utils, body, posKey }) => `${commonShaderContent} + content: ({ uniforms, utils, body, posKey, maskKey, threshold }) => `${commonShaderContent} @group(0) @binding(0) var vertices: array; @group(0) @binding(1) var indices: array; @group(1) @binding(0) var meshUniforms : MeshUniforms; -@group(2) @binding(0) var instance_pts: array; -@group(2) @binding(1) var instanceCount: u32; +@group(2) @binding(0) +var instance_pts: array; +@group(2) @binding(1) +var instanceCount: u32; +// @group(2) @binding(2) +// var createdInstances: atomic; ${uniforms} @@ -18,27 +22,123 @@ fn vertexOffset(i: u32) -> u32 { return i * 8u; } +fn hash11(n: f32) -> f32 { + let x = fract(sin(n) * 43758.5453123); + return x; +} + ${utils} +fn generateCode(v: u32) -> f32 { + let terrainPos = vec3f( + vertices[v], + 0.0, + vertices[v + 2u], + ); + + ${body} + + let maskKey = ${maskKey}; + return maskKey; +} + @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3) { if (id.x >= instanceCount) { return; } -${body} - - let vOffset = vertexOffset(id.x); - let instancePos = ${posKey}; - - instance_pts[vOffset + 0] = ${posKey}.x; - instance_pts[vOffset + 1] = ${posKey}.y; - instance_pts[vOffset + 2] = ${posKey}.z; - instance_pts[vOffset + 3] = 0.0; // nor.x - instance_pts[vOffset + 4] = 1.0; // nor.y - instance_pts[vOffset + 5] = 0.0; // nor.z - instance_pts[vOffset + 6] = 0.0; // uv.x - instance_pts[vOffset + 7] = 0.0; // uv.y + let offset = vertexOffset(id.x); + + let terrainPos = vec3f( + vertices[offset], + vertices[offset + 1u], + vertices[offset + 2u], + ); + + ${body} + + // let instancePos = ${posKey}; + let maskKey = ${maskKey}; + let threshold = f32(${threshold}); + + let subdivisions = u32(meshUniforms.resolution); + let size = meshUniforms.size; + let step = size / f32(subdivisions); + + // generate a random point inside the grid + let rx = hash11(f32(id.x + 1) * 12.123); + let rz = hash11(f32(id.x + 1) * 91.331); + + // world pos + let x = rx * size - size * 0.5; + let z = rz * size - size * 0.5; + + // convert to local + let lx = x + size * 0.5; + let lz = z + size * 0.5; + + // get grid cell + let cx = clamp(i32(floor(lx / step)), 0, i32(subdivisions - 1u)); + let cz = clamp(i32(floor(lz / step)), 0, i32(subdivisions - 1u)); + + let verts_per_row = subdivisions + 1u; + let cx1 = min(u32(cx) + 1u, subdivisions); + let cz1 = min(u32(cz) + 1u, subdivisions); + + // find the idxes of the 4 corners + let idx_bl = u32(cz) * verts_per_row + u32(cx); // bottom-left + let idx_br = u32(cz) * verts_per_row + cx1; // bottom-right + let idx_tl = cz1 * verts_per_row + u32(cx); // top-left + let idx_tr = cz1 * verts_per_row + cx1; // top-right + + // find vertex offset of each of these + let v_bl = vertexOffset(idx_bl); + let v_br = vertexOffset(idx_br); + let v_tl = vertexOffset(idx_tl); + let v_tr = vertexOffset(idx_tr); + + // run generated code on each of them also. this kinda sucks + let mask_bl = generateCode(v_bl); + let mask_br = generateCode(v_br); + let mask_tl = generateCode(v_tl); + let mask_tr = generateCode(v_tr); + + let h_bl = vertices[v_bl + 1]; + let h_br = vertices[v_br + 1]; + let h_tl = vertices[v_tl + 1]; + let h_tr = vertices[v_tr + 1]; + + // normals for each point also + let nor_bl = vec3f(vertices[v_bl + 3], vertices[v_bl + 4], vertices[v_bl + 5]); + let nor_br = vec3f(vertices[v_br + 3], vertices[v_br + 4], vertices[v_br + 5]); + let nor_tl = vec3f(vertices[v_tl + 3], vertices[v_tl + 4], vertices[v_tl + 5]); + let nor_tr = vec3f(vertices[v_tr + 3], vertices[v_tr + 4], vertices[v_tr + 5]); + + // fractions... + let fx = fract(lx / step); + let fz = fract(lz / step); + + let height = mix(mix(h_bl, h_br, fx), mix(h_tl, h_tr, fx), fz); + let mask = mix(mix(mask_bl, mask_br, fx), mix(mask_tl, mask_tr, fx), fz); + let normal = normalize(mix(mix(nor_bl, nor_br, fx), mix(nor_tl, nor_tr, fx), fz)); + + // do all the annoying calc in here, then store it in instance_pts... + let helper = select(vec3f(0.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), abs(normal.y) > 0.99); + let T = normalize(cross(helper, normal)); + let B = cross(normal, T); + let rot = mat3x3f(T, B, normal); + + instance_pts[id.x].pos = vec3(x, height, z); + instance_pts[id.x].nor = vec3(normal.x, normal.y, normal.z); + instance_pts[id.x].uv = vec2(0.0, 0.0); + instance_pts[id.x].rotMat = rot; + + if (mask < threshold) { + instance_pts[id.x].used = 0u; + } else { + instance_pts[id.x].used = 1u; + } } `, }; diff --git a/src/lib/shaders/jit/types/shaders.ts b/src/lib/shaders/jit/types/shaders.ts index 45d0439..d2750a0 100644 --- a/src/lib/shaders/jit/types/shaders.ts +++ b/src/lib/shaders/jit/types/shaders.ts @@ -28,6 +28,8 @@ export type InstancingShaderConfig = ShaderConfigBase & { instancePositions: util.ReferenceKey; meshPath: string; fileContent?: string; + maskKey?: util.ReferenceKey; + threshold?: number; transform?: TransformConfig; fileType?: string; }; @@ -45,6 +47,8 @@ type InstancingShaderTemplateValues = { utils: string; body: string; posKey: string; + maskKey?: string; + threshold?: number; transformCode?: string; }; diff --git a/src/lib/webgpu-context.ts b/src/lib/webgpu-context.ts index fb8c930..bb87cdb 100644 --- a/src/lib/webgpu-context.ts +++ b/src/lib/webgpu-context.ts @@ -43,6 +43,7 @@ export function initWebGPU(device: GPUDevice, canvas: HTMLCanvasElement): WebGPU context.configure({ device, format: canvasFormat, + alphaMode: 'premultiplied', }); console.log('WebGPU init successsful');