From b897a37325f21e7423d7f5809a4f936258a29602 Mon Sep 17 00:00:00 2001 From: Andrew Ray Date: Sun, 26 Jul 2026 12:00:50 -0700 Subject: [PATCH] Adding ability to export shaders Adds a FrogMaterial, and engineNodeProperties, and makes filling for engine nodes a no-op, but it does still parse the megashader, to dedupe uniforms in user-land GLSL. --- package-lock.json | 1482 ++++++++++++++++++++++++++- package.json | 4 +- src/engine.ts | 38 +- src/graph/graph.test.ts | 7 - src/graph/graph.ts | 209 ++-- src/graph/parsers.ts | 35 +- src/plugins/three/FrogMaterial.ts | 300 ++++++ src/plugins/three/index.ts | 6 + src/plugins/three/threngine.test.ts | 2 - src/plugins/three/threngine.ts | 586 ++++++++--- src/strategy/assignmentTo.ts | 17 +- src/util/string.ts | 18 + tsconfig.json | 4 +- 13 files changed, 2370 insertions(+), 338 deletions(-) create mode 100644 src/plugins/three/FrogMaterial.ts create mode 100644 src/util/string.ts diff --git a/package-lock.json b/package-lock.json index 1337dc6..2c4273c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@shaderfrog/core", - "version": "3.1.1", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@shaderfrog/core", - "version": "3.1.1", + "version": "3.2.0", "license": "ISC", "dependencies": { "lodash.groupby": "^4.6.0" @@ -18,8 +18,10 @@ "@shaderfrog/glsl-parser": "^7.0.1", "@swc/core": "^1.6.7", "@types/lodash.groupby": "^4.6.7", + "@types/node": "^20.1.2", "@types/three": "^0.169.0", "babylonjs": "^6.2.0", + "playcanvas": "^1.65.3", "prettier": "^3.3.2", "three": "^0.169.0", "typescript": "^5.5.3", @@ -2139,6 +2141,42 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -2605,12 +2643,14 @@ } }, "node_modules/@types/node": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.2.tgz", - "integrity": "sha512-CTO/wa8x+rZU626cL2BlbCDzydgnFNgc19h4YvizpTO88MFQxab8wqisxaofQJ/9bLGugRdWIuX/TbIs6VVF6g==", + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, - "optional": true, - "peer": true + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/stats.js": { "version": "0.17.0", @@ -2634,10 +2674,11 @@ } }, "node_modules/@types/webxr": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", - "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==", - "dev": true + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" }, "node_modules/@vitest/expect": { "version": "1.6.0", @@ -2736,12 +2777,20 @@ } }, "node_modules/@webgpu/types": { - "version": "0.1.46", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.46.tgz", - "integrity": "sha512-2iogO6Zh0pTbKLGZuuGWEmJpF/fTABGs7G9wXxpn7s24XSJchSUIiMqIJHURi5zsMZRRTuXrV/3GLOkmOFjq5w==", + "version": "0.1.71", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", + "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", "dev": true, "license": "BSD-3-Clause" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -2766,6 +2815,31 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -2778,6 +2852,30 @@ "node": ">=4" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -2833,6 +2931,26 @@ "dev": true, "hasInstallScript": true }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -2890,6 +3008,23 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -2934,6 +3069,17 @@ "node": "*" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2949,12 +3095,39 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/confbox": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", "dev": true }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -3005,6 +3178,20 @@ } } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -3017,6 +3204,25 @@ "node": ">=6" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -3032,6 +3238,14 @@ "integrity": "sha512-xZ0y4zjWZgp65okzwwt00f2rYibkFPHUv9qBz+Vzn8cB9UXIo9Zc6Dw81LJYhhNt0G/vR1OJEfStZ49NKl0YxQ==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -3113,6 +3327,50 @@ "dev": true, "license": "MIT" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3133,6 +3391,29 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3151,6 +3432,29 @@ "node": "*" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3181,6 +3485,50 @@ "node": ">=4" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/is-core-module": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", @@ -3193,6 +3541,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3283,6 +3642,23 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3295,24 +3671,122 @@ "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", "dev": true }, - "node_modules/mlly": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", - "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", + "optional": true, "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.1.1", - "ufo": "^1.5.3" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/ms": { + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -3331,12 +3805,99 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3385,11 +3946,21 @@ } }, "node_modules/playcanvas": { - "version": "1.65.3", - "resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-1.65.3.tgz", - "integrity": "sha512-ODxhVqYUe0mFcIFPcZhp0lmvSif/iLA2Y5/M3i0p0Ru1R1PZ+nLMyyxKqPGeCBqXh1Kqg5K4co0+Qk0y6BZd9Q==", - "optional": true, - "peer": true + "version": "1.77.0", + "resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-1.77.0.tgz", + "integrity": "sha512-cj71KZ2pRpA9Hh+oYQ7UsWgs69P8FtBoLCbrntY2Eqx9WL4Yi8+90qGj5eWp8eQnGdGHcZXXrUgLeIT2ILXdhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/webxr": "^0.5.20", + "@webgpu/types": "^0.1.51" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "canvas": "^2.11.2" + } }, "node_modules/postcss": { "version": "8.4.39", @@ -3466,6 +4037,22 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -3554,6 +4141,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", @@ -3589,6 +4194,28 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -3598,6 +4225,14 @@ "semver": "bin/semver.js" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3625,6 +4260,49 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -3646,6 +4324,47 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-literal": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", @@ -3688,6 +4407,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/three": { "version": "0.169.0", "resolved": "https://registry.npmjs.org/three/-/three-0.169.0.tgz", @@ -3728,6 +4475,14 @@ "node": ">=4" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -3756,6 +4511,13 @@ "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -3826,6 +4588,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/vite": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", @@ -4102,6 +4872,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4133,6 +4923,25 @@ "node": ">=8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -5509,6 +6318,33 @@ } } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "optional": true + } + } + }, "@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -5766,12 +6602,13 @@ } }, "@types/node": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.2.tgz", - "integrity": "sha512-CTO/wa8x+rZU626cL2BlbCDzydgnFNgc19h4YvizpTO88MFQxab8wqisxaofQJ/9bLGugRdWIuX/TbIs6VVF6g==", + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, - "optional": true, - "peer": true + "requires": { + "undici-types": "~6.21.0" + } }, "@types/stats.js": { "version": "0.17.0", @@ -5794,9 +6631,9 @@ } }, "@types/webxr": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", - "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==", + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", "dev": true }, "@vitest/expect": { @@ -5871,12 +6708,19 @@ } }, "@webgpu/types": { - "version": "0.1.46", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.46.tgz", - "integrity": "sha512-2iogO6Zh0pTbKLGZuuGWEmJpF/fTABGs7G9wXxpn7s24XSJchSUIiMqIJHURi5zsMZRRTuXrV/3GLOkmOFjq5w==", + "version": "0.1.71", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", + "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", "dev": true }, - "acorn": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, + "acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", @@ -5891,6 +6735,23 @@ "acorn": "^8.11.0" } }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "optional": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -5900,6 +6761,24 @@ "color-convert": "^1.9.0" } }, + "aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -5942,6 +6821,24 @@ "integrity": "sha512-TtUbRonHjEdJ2AzlQ1LpDHgnJAyrugDIcWLLPIy768JRoEM2WNlp6GcK7dv1ke/o9RKR3TNRodK/bMXL+h6WHw==", "dev": true }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -5966,6 +6863,18 @@ "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", "dev": true }, + "canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + } + }, "chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -6001,6 +6910,13 @@ "get-func-name": "^2.0.2" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "optional": true + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6016,12 +6932,33 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "optional": true + }, "confbox": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "optional": true + }, "convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -6057,6 +6994,16 @@ "ms": "2.1.2" } }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, "deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -6066,6 +7013,20 @@ "type-detect": "^4.0.0" } }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "optional": true + }, "diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -6078,6 +7039,13 @@ "integrity": "sha512-xZ0y4zjWZgp65okzwwt00f2rYibkFPHUv9qBz+Vzn8cB9UXIo9Zc6Dw81LJYhhNt0G/vR1OJEfStZ49NKl0YxQ==", "dev": true }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "optional": true + }, "esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -6142,6 +7110,42 @@ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "optional": true + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6155,6 +7159,24 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6167,6 +7189,21 @@ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6188,6 +7225,42 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "optional": true + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "optional": true + }, "is-core-module": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", @@ -6197,6 +7270,13 @@ "has": "^1.0.3" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "optional": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6269,6 +7349,16 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "optional": true, + "requires": { + "semver": "^6.0.0" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6281,6 +7371,67 @@ "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", "dev": true }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "optional": true + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "optional": true + }, "mlly": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", @@ -6299,18 +7450,82 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "dev": true, + "optional": true + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "optional": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6353,11 +7568,15 @@ } }, "playcanvas": { - "version": "1.65.3", - "resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-1.65.3.tgz", - "integrity": "sha512-ODxhVqYUe0mFcIFPcZhp0lmvSif/iLA2Y5/M3i0p0Ru1R1PZ+nLMyyxKqPGeCBqXh1Kqg5K4co0+Qk0y6BZd9Q==", - "optional": true, - "peer": true + "version": "1.77.0", + "resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-1.77.0.tgz", + "integrity": "sha512-cj71KZ2pRpA9Hh+oYQ7UsWgs69P8FtBoLCbrntY2Eqx9WL4Yi8+90qGj5eWp8eQnGdGHcZXXrUgLeIT2ILXdhw==", + "dev": true, + "requires": { + "@types/webxr": "^0.5.20", + "@webgpu/types": "^0.1.51", + "canvas": "^2.11.2" + } }, "postcss": { "version": "8.4.39", @@ -6401,6 +7620,18 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -6473,6 +7704,16 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, "rollup": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", @@ -6499,12 +7740,26 @@ "fsevents": "~2.3.2" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "optional": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "optional": true + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6526,6 +7781,32 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "optional": true + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "optional": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -6544,6 +7825,38 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-literal": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", @@ -6576,6 +7889,30 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + } + } + }, "three": { "version": "0.169.0", "resolved": "https://registry.npmjs.org/three/-/three-0.169.0.tgz", @@ -6606,6 +7943,13 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "optional": true + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -6624,6 +7968,12 @@ "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -6662,6 +8012,13 @@ "picocolors": "^1.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "optional": true + }, "vite": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", @@ -6794,6 +8151,24 @@ } } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "optional": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "optional": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6813,6 +8188,23 @@ "stackback": "0.0.2" } }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "optional": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 7816469..8c15318 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shaderfrog/core", - "version": "3.2.0", + "version": "4.0.0", "description": "Shaderfrog core", "type": "module", "files": [ @@ -37,8 +37,10 @@ "@shaderfrog/glsl-parser": "^7.0.1", "@swc/core": "^1.6.7", "@types/lodash.groupby": "^4.6.7", + "@types/node": "^20.1.2", "@types/three": "^0.169.0", "babylonjs": "^6.2.0", + "playcanvas": "^1.65.3", "prettier": "^3.3.2", "three": "^0.169.0", "typescript": "^5.5.3", diff --git a/src/engine.ts b/src/engine.ts index bc57c07..026635f 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -31,7 +31,7 @@ export type PhongNodeConstructor = ( name: string, position: NodePosition, uniforms: UniformDataType[], - stage: ShaderStage | undefined + stage: ShaderStage | undefined, ) => CodeNode; export type PhysicalNodeConstructor = ( @@ -39,7 +39,7 @@ export type PhysicalNodeConstructor = ( name: string, position: NodePosition, uniforms: UniformDataType[], - stage: ShaderStage | undefined + stage: ShaderStage | undefined, ) => CodeNode; export type ToonNodeConstructor = ( @@ -47,7 +47,7 @@ export type ToonNodeConstructor = ( name: string, position: NodePosition, uniforms: UniformDataType[], - stage: ShaderStage | undefined + stage: ShaderStage | undefined, ) => CodeNode; export interface Engine { @@ -90,18 +90,12 @@ export type EngineContext = { engine: string; nodes: Record; runtime: T; - debuggingNonsense: { - vertexSource?: string; - vertexPreprocessed?: string; - fragmentPreprocessed?: string; - fragmentSource?: string; - }; }; export const extendNodeContext = ( context: EngineContext, nodeId: string, - nodeContext: Partial + nodeContext: Partial, ) => ({ ...context, nodes: { @@ -114,7 +108,7 @@ export const extendNodeContext = ( }); export const extendNodesContext = ( context: EngineContext, - nodesContext: NodeContexts + nodesContext: NodeContexts, ) => ({ ...context, nodes: { @@ -128,7 +122,7 @@ export type EngineImporter = { ast: Program, options?: Record & { type?: ShaderStage; - } + }, ): void; nodeInputMap: Partial>>; edgeMap: { [oldInput: string]: string }; @@ -143,7 +137,7 @@ export type EngineImporters = { export const convertNode = ( node: SourceNode, - converter: EngineImporter + converter: EngineImporter, ): SourceNode => { log(`Converting ${node.name} (${node.id})`); const preprocessed = preprocess(node.source, { @@ -166,12 +160,12 @@ export const convertNode = ( export const convertToEngine = ( oldEngine: Engine, newEngine: Engine, - graph: Graph + graph: Graph, ): Graph => { const converter = newEngine.importers[oldEngine.name]; if (!converter) { throw new Error( - `The engine ${newEngine.name} has no importer for ${oldEngine.name}` + `The engine ${newEngine.name} has no importer for ${oldEngine.name}`, ); } @@ -194,12 +188,12 @@ export const convertToEngine = ( source.name, source.position, source.config.uniforms, - source.stage + source.stage, ); // Bail if no conversion } else { throw new Error( - `Can't convert ${oldEngine.name} to ${newEngine.name} because ${newEngine.name} does not have a "${node.type}" constructor` + `Can't convert ${oldEngine.name} to ${newEngine.name} because ${newEngine.name} does not have a "${node.type}" constructor`, ); } } else if (NodeType.SOURCE === node.type) { @@ -224,7 +218,7 @@ export const convertToEngine = ( edge.input, 'as there is no edge mapping in the', newEngine.name, - 'importer' + 'importer', ); edgeUpdates[edge.id] = null; } @@ -277,7 +271,7 @@ export type DefaultPropertySetter = (p: NodeProperty) => any; export const collectInitialEvaluatedGraphProperties = ( engine: Engine, graph: Graph, - defaultPropertySetting: DefaultPropertySetter + defaultPropertySetting: DefaultPropertySetter, ) => { const graphProperties: Record = {}; @@ -294,7 +288,7 @@ export const collectInitialEvaluatedGraphProperties = ( // predicate search enforces const input = i as NodeInput & { property: string }; const edge = graph.edges.find( - ({ to, input: i }) => to === node.id && i === input.id + ({ to, input: i }) => to === node.id && i === input.id, ); // In the case where a node has been deleted from the graph, // dataInputs won't have been udpated until a recompile completes @@ -306,7 +300,7 @@ export const collectInitialEvaluatedGraphProperties = ( // Find the corresponding property on the node and get the default // setting const property = (node.config.properties || []).find( - (p) => p.property === input.property + (p) => p.property === input.property, ); if (property) { graphProperties[input.property] = defaultPropertySetting(property); @@ -320,7 +314,7 @@ export const collectInitialEvaluatedGraphProperties = ( graphProperties[input.property] = evaluateNode( engine, graph, - fromNode + fromNode, ); } catch (err) { console.error('Tried to evaluate a non-data node!', { diff --git a/src/graph/graph.test.ts b/src/graph/graph.test.ts index 3f5fff2..c85ba1b 100644 --- a/src/graph/graph.test.ts +++ b/src/graph/graph.test.ts @@ -179,7 +179,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, engine, engineContext); @@ -236,7 +235,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, engine, engineContext); @@ -313,7 +311,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const preserver = { ...engine, preserve: new Set(['vUv']) }; @@ -388,7 +385,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, engine, engineContext); @@ -449,7 +445,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, engine, engineContext); @@ -503,7 +498,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, engine, engineContext); @@ -546,7 +540,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, engine, engineContext); diff --git a/src/graph/graph.ts b/src/graph/graph.ts index e052362..54c21b9 100644 --- a/src/graph/graph.ts +++ b/src/graph/graph.ts @@ -66,14 +66,14 @@ export const findNode = (graph: Graph, id: string): GraphNode => export const doesLinkThruShader = (graph: Graph, node: GraphNode): boolean => { const edges = graph.edges.filter( - (edge) => edge.type !== EdgeLink.NEXT_STAGE && edge.from === node.id + (edge) => edge.type !== EdgeLink.NEXT_STAGE && edge.from === node.id, ); if (edges.length === 0) { return false; } return edges.reduce((foundShader, edge: Edge) => { const upstreamNode = ensure( - graph.nodes.find((node) => node.id === edge.to) + graph.nodes.find((node) => node.id === edge.to), ); return ( foundShader || @@ -97,7 +97,7 @@ export const resultName = (node: GraphNode): string => nodeName(node) + '_out'; export const mangleName = ( name: string, node: GraphNode, - nextSibling?: GraphNode + nextSibling?: GraphNode, ) => { // Mangle a name to its next stage node, so the vertex suffix becomes the // fragment id, but not the other way around. @@ -112,17 +112,17 @@ export const mangleVar = ( name: string, engine: Engine, node: GraphNode, - sibling?: GraphNode + sibling?: GraphNode, ) => (engine.preserve.has(name) ? name : mangleName(name, node, sibling)); export const mangleEntireProgram = ( engine: Engine, ast: FrogProgram, node: GraphNode, - sibling?: GraphNode + sibling?: GraphNode, ) => { ast.scopes[0].bindings = renameBindings(ast.scopes[0].bindings, (name) => - name === ast.outVar ? name : mangleVar(name, engine, node, sibling) + name === ast.outVar ? name : mangleVar(name, engine, node, sibling), ); mangleMainFn(ast, node, sibling); }; @@ -130,17 +130,17 @@ export const mangleEntireProgram = ( export const mangleMainFn = ( ast: Program, node: GraphNode, - sibling?: GraphNode + sibling?: GraphNode, ) => { ast.scopes[0].functions = renameFunctions(ast.scopes[0].functions, (name) => - name === 'main' ? nodeName(node) : mangleName(name, node, sibling) + name === 'main' ? nodeName(node) : mangleName(name, node, sibling), ); }; export const ensureFromNode = (graph: Graph, inputEdge: Edge) => ensure( graph.nodes.find(({ id }) => id === inputEdge.from), - `Orphaned edge! There is an edge fro "${inputEdge.from}" to "${inputEdge.to}", but from node ${inputEdge.from} does not exist in the graph.` + `Orphaned edge! There is an edge fro "${inputEdge.from}" to "${inputEdge.to}", but from node ${inputEdge.from} does not exist in the graph.`, ); export const resetGraphIds = (graph: Graph): Graph => { @@ -188,7 +188,7 @@ export const collapseBinaryGraphEdges = (graph: Graph): Graph => { } : acc; }, - {} + {}, ); // Then collapse them @@ -214,7 +214,7 @@ export const addEdgeAndPruneRestrictions = (edges: Edge[], newEdge: Edge) => .filter( (edge) => // Prevent one input handle from having multiple inputs - !(edge.to === newEdge.to && edge.input === newEdge.input) + !(edge.to === newEdge.to && edge.input === newEdge.input), ) .concat(newEdge); @@ -233,7 +233,7 @@ export const addGraphEdge = (graph: Graph, newEdge: Edge): Graph => export const findLinkedNode = (graph: Graph, nodeId: string) => { const edgeLink = graph.edges.find( (e) => - e.type === EdgeLink.NEXT_STAGE && (e.from === nodeId || e.to === nodeId) + e.type === EdgeLink.NEXT_STAGE && (e.from === nodeId || e.to === nodeId), ); const otherId = edgeLink?.from === nodeId ? edgeLink?.to : edgeLink?.from; @@ -246,15 +246,14 @@ export const findLinkedNode = (graph: Graph, nodeId: string) => { */ export const findLinkedVertexNodes = ( graph: Graph, - existingIds: NodeIds = {} + existingIds: NodeIds = {}, ) => { // Group edges by where they point const edgeLinks = graph.edges .filter((e) => e.type === EdgeLink.NEXT_STAGE) - .reduce>( - (edges, edge) => ({ ...edges, [edge.to]: edge, [edge.from]: edge }), - {} - ); + .reduce< + Record + >((edges, edge) => ({ ...edges, [edge.to]: edge, [edge.from]: edge }), {}); return graph.nodes.filter( (node) => @@ -264,7 +263,7 @@ export const findLinkedVertexNodes = ( // That's linked node.id in edgeLinks && // And not already captured (this should probably just be a set) - !existingIds[node.id] + !existingIds[node.id], ); }; @@ -272,21 +271,21 @@ export type Predicates = { node?: ( node: GraphNode, inputEdges: Edge[], - lastResult: SearchResult + lastResult: SearchResult, ) => boolean; edge?: ( input: NodeInput | undefined, node: GraphNode, inputEdge: Edge | undefined, fromNode: GraphNode | undefined, - lastResult: SearchResult + lastResult: SearchResult, ) => boolean; input?: ( input: NodeInput, node: GraphNode, inputEdge: Edge | undefined, fromNode: GraphNode | undefined, - lastResult: SearchResult + lastResult: SearchResult, ) => boolean; }; export type SearchResult = { @@ -305,7 +304,7 @@ export const consSearchResult = (): SearchResult => ({ }); export const mergeSearchResults = ( a: SearchResult, - b: SearchResult + b: SearchResult, ): SearchResult => ({ nodes: { ...a.nodes, ...b.nodes }, inputs: { ...a.inputs, ...b.inputs }, @@ -327,10 +326,11 @@ export const prepopulatePropertyInputs = (node: CodeNode): CodeNode => ({ `property_${property.property}`, 'property', property.type, - ['data'], - !!property.fillerName, // bakeable - property.property - ) + // Allow any code to plug into engine node properties + ['data', 'code'], + true, // bakeable + property.property, + ), ), ], }); @@ -347,7 +347,7 @@ export const filterGraphFromNode = ( node: GraphNode, predicates: Predicates, depth = Infinity, - lastResult = consSearchResult() + lastResult = consSearchResult(), ): SearchResult => { const { inputs } = node; const inputEdges = graph.edges.filter((edge) => edge.to === node.id); @@ -398,7 +398,7 @@ export const filterGraphFromNode = ( fromNode, predicates, depth - 1, - intermediateAcc + intermediateAcc, ); // The result is automatically the combination of the currenet acc and the @@ -420,7 +420,7 @@ const merge = (a: Record, b: Record) => { ...acc, [key]: [...(a[key] || []), ...(b[key] || [])], }), - {} + {}, ); }; @@ -428,7 +428,7 @@ export const filterGraphNodes = ( graph: Graph, nodes: GraphNode[], filter: Predicates, - depth = Infinity + depth = Infinity, ) => nodes.reduce((acc, node) => { const result = filterGraphFromNode(graph, node, filter, depth); @@ -450,7 +450,7 @@ export type CompileNodeResult = [ filler: ReturnType, // All of the nodes compiled as dependencies of this node, continues to grow // as the graph is compiled. - compiledIds: NodeIds + compiledIds: NodeIds, ]; // before data inputs were known by the input.category being node or data. I @@ -465,7 +465,8 @@ export const compileNode = ( edges: Edge[], engineContext: EngineContext, node: GraphNode, - activeIds: NodeIds = {} + engineNodeProperties: EngineNodeProperties, + activeIds: NodeIds = {}, ): CompileNodeResult => { // THIS DUPLICATES OTHER LINE const parser = { @@ -479,7 +480,7 @@ export const compileNode = ( if (!parser) { console.error(node); throw new Error( - `No parser found for ${node.name} (${node.type}, id ${node.id})` + `No parser found for ${node.name} (${node.type}, id ${node.id})`, ); } @@ -487,12 +488,12 @@ export const compileNode = ( ? null : ensure( engineContext.nodes[node.id], - `No node context found for "${node.name}" (id ${node.id})!` + `No node context found for "${node.name}" (id ${node.id})!`, ); const { ast, inputFillers } = nodeContext || {}; if (!ast || !inputFillers) { throw new Error( - "I'm drunk while writing this code, and I think this case should be impossible, so I'm going to throw an error and ignore it" + "I'm drunk while writing this code, and I think this case should be impossible, so I'm going to throw an error and ignore it", ); } @@ -500,6 +501,7 @@ export const compileNode = ( const inputEdges = edges.filter((edge) => edge.to === node.id); let continuation = shaderSectionsCons(); + const isEngineNode = isSourceNode(node) && node.engine; // Compile children recursively inputEdges @@ -508,7 +510,7 @@ export const compileNode = ( edge, fromNode: ensure( graph.nodes.find((node) => edge.from === node.id), - `GraphNode for edge ${edge.from} not found` + `GraphNode for edge ${edge.from} not found`, ), input: ensure( inputs.find(({ id }) => id == edge.input), @@ -516,7 +518,7 @@ export const compileNode = ( (node as SourceNode).stage ? ` (${(node as SourceNode).stage})` : '' } has no input ${edge.input}!\nAvailable:${inputs .map(({ id }) => id) - .join(', ')}` + .join(', ')}`, ), })) .filter(({ input }) => !isDataInput(input)) @@ -527,11 +529,12 @@ export const compileNode = ( edges, engineContext, fromNode, - activeIds + engineNodeProperties, + activeIds, ); if (!fillerFn) { throw new TypeError( - `Expected a filler ast from node ID ${fromNode.id} (${fromNode.type}) but none was returned` + `Expected a filler ast from node ID ${fromNode.id} (${fromNode.type}) but none was returned`, ); } @@ -543,15 +546,39 @@ export const compileNode = ( return; } + // Don't backfill by discarding the backfiller args + let finalFillerFn = () => fillerFn(); + + // Test if it needs to be backfilled - this only goes one level deep + // because we're only backfilling fromNode + let backfillers = codeNode.backfillers?.[input.id]; + if (backfillers && shouldNodeHaveMainFn(fromNode)) { + const childAst = engineContext.nodes[fromNode.id].ast; + // For now we can only backfill programs + if (childAst.type === 'program') { + backfillers.forEach((backfiller) => { + // This is where the variable name gets injected into the main + // function parameter of the backfilled child + backfillAst( + childAst, + backfiller.argType, + backfiller.targetVariable, + engineContext.nodes[fromNode.id].mainFn, + ); + }); + } + finalFillerFn = fillerFn; + } + // Produce the input filler let filler: InputFillerGroup; let fillerName: string | undefined; if (input.property) { fillerName = ensure( (codeNode.config.properties || []).find( - (p) => p.property === input.property + (p) => p.property === input.property, )?.fillerName, - `Node "${node.name}" has no property named "${input.property}" to find the filler for` + `Node "${node.name}" has no property named "${input.property}" to find the filler for`, ); filler = inputFillers[fillerName]; } else { @@ -565,35 +592,19 @@ export const compileNode = ( fillerName, }); return; - // throw new Error( - // `Node "${node.name}"${ - // (node as SourceNode).stage ? ` (${(node as SourceNode).stage})` : '' - // } has no filler for input "${input.displayName}" named ${fillerName}`, - // ); } - // Test if it needs to be backfilled - this only goes one level deep - // because we're only backfilling fromNode - let backfillers = codeNode.backfillers?.[input.id]; - if (backfillers && shouldNodeHaveMainFn(fromNode)) { - const childAst = engineContext.nodes[fromNode.id].ast; - // For now we can only backfill programs - if (childAst.type === 'program') { - backfillers.forEach((backfiller) => { - // This is where the variable name gets injected into the main - // function parameter of the backfilled child - backfillAst( - childAst, - backfiller.argType, - backfiller.targetVariable, - engineContext.nodes[fromNode.id].mainFn - ); - }); - } - nodeContext.ast = filler.filler(fillerFn); + if (isEngineNode) { + // Migration hack: property_map and filler_position -> map and position + const totalHackPropertyName = input.id.includes('_') + ? input.id.split('_')[1] + : input.id; + engineNodeProperties[totalHackPropertyName] = { + fillerGroup: filler, + result: generate(finalFillerFn() ?? []), + }; } else { - // Don't backfill by discarding the backfiller args - nodeContext.ast = filler.filler(() => fillerFn()); + nodeContext.ast = filler.filler(finalFillerFn); } }); @@ -602,10 +613,12 @@ export const compileNode = ( const sections = mergeShaderSections( continuation, isDataNode(node) || + // Engide nodes don't get merged in + isEngineNode || codeNode.sourceType === SourceType.EXPRESSION || codeNode.sourceType === SourceType.FN_BODY_FRAGMENT ? shaderSectionsCons() - : findShaderSections(node.id, ast as Program) + : findShaderSections(node.id, ast as Program), ); const filler: Filler = isDataNode(node) @@ -622,17 +635,20 @@ export type CompileGraphResult = { outputVert: GraphNode; orphanNodes: GraphNode[]; activeNodeIds: Set; + engineNodeProperties: EngineNodeProperties; }; export const compileGraph = ( engineContext: EngineContext, engine: Engine, - graph: Graph + graph: Graph, ): CompileGraphResult => { // computeGraphContext(engineContext, engine, graph); + const engineNodeProperties = {}; + const outputFrag = graph.nodes.find( - (node) => node.type === 'output' && node.stage === 'fragment' + (node) => node.type === 'output' && node.stage === 'fragment', ); if (!outputFrag) { throw new Error('No fragment output in graph'); @@ -643,11 +659,12 @@ export const compileGraph = ( graph, graph.edges, engineContext, - outputFrag + outputFrag, + engineNodeProperties, ); const outputVert = graph.nodes.find( - (node) => node.type === 'output' && node.stage === 'vertex' + (node) => node.type === 'output' && node.stage === 'vertex', ); if (!outputVert) { throw new Error('No vertex output in graph'); @@ -676,22 +693,24 @@ export const compileGraph = ( graph, [...graph.edges, ...orphanEdges], engineContext, - outputVert + outputVert, + engineNodeProperties, ); - // Every compileNode returns the AST so far, as well as the filler for the - // next node with inputs. On the final step, we discard the filler + const allCompiledIds = [ + ...Object.keys(vertexIds), + ...Object.keys(fragmentIds), + ...orphanNodes.map((node) => node.id), + ]; + return { fragment, vertex, outputFrag, outputVert, orphanNodes, - activeNodeIds: new Set([ - ...Object.keys(vertexIds), - ...Object.keys(fragmentIds), - ...orphanNodes.map((node) => node.id), - ]), + activeNodeIds: new Set(allCompiledIds), + engineNodeProperties, }; }; @@ -712,20 +731,20 @@ export const collectNodeProperties = (graph: Graph): SearchResult => { }; const outputFrag = graph.nodes.find( - (node) => node.type === 'output' && node.stage === 'fragment' + (node) => node.type === 'output' && node.stage === 'fragment', ) as GraphNode; const outputVert = graph.nodes.find( - (node) => node.type === 'output' && node.stage === 'vertex' + (node) => node.type === 'output' && node.stage === 'vertex', ) as GraphNode; const fragProperties = filterGraphFromNode( graph, outputFrag, - nodesWithProperties + nodesWithProperties, ); const vertProperties = filterGraphFromNode( graph, outputVert, - nodesWithProperties + nodesWithProperties, ); return { @@ -737,6 +756,17 @@ export const collectNodeProperties = (graph: Graph): SearchResult => { export type IndexedDataInputs = Record; +// The megadshader is parsed, but not included in final GLSL composition. +// Instead, we track the fillers into the engine nodes, and use those to compute +// the property map to pass to FrogMaterial +export type EngineNodeProperties = Record< + string, + { + fillerGroup: InputFillerGroup; + result: any; + } +>; + export type CompileResult = { updatedNodeContext: NodeContexts; fragmentResult: string; @@ -750,7 +780,7 @@ export type CompileResult = { export const compileSource = async ( graph: Graph, engine: Engine, - ctx: EngineContext + ctx: EngineContext, ): Promise => { const result = await computeGraphContext(ctx, engine, graph); if (isError(result)) { @@ -765,16 +795,17 @@ export const compileSource = async ( const compileResult = compileGraph(updatedContext, engine, graph); const fragmentResult = generate( - shaderSectionsToProgram(compileResult.fragment, engine.mergeOptions).program + shaderSectionsToProgram(compileResult.fragment, engine.mergeOptions) + .program, ); const vertexResult = generate( - shaderSectionsToProgram(compileResult.vertex, engine.mergeOptions).program + shaderSectionsToProgram(compileResult.vertex, engine.mergeOptions).program, ); const dataInputs = filterGraphNodes( graph, [compileResult.outputFrag, compileResult.outputVert], - { input: isDataInput } + { input: isDataInput }, ).inputs; // Find which nodes flow up into uniform inputs, for colorizing and for @@ -784,7 +815,7 @@ export const compileSource = async ( >((acc, [nodeId, inputs]) => { return inputs.reduce((iAcc, input) => { const fromEdge = graph.edges.find( - (edge) => edge.to === nodeId && edge.input === input.id + (edge) => edge.to === nodeId && edge.input === input.id, ); const fromNode = fromEdge && graph.nodes.find((node) => node.id === fromEdge.from); diff --git a/src/graph/parsers.ts b/src/graph/parsers.ts index cb34ca7..7252fe9 100644 --- a/src/graph/parsers.ts +++ b/src/graph/parsers.ts @@ -43,14 +43,14 @@ export type ProduceAst = ( engine: Engine, graph: Graph, node: SourceNode, - inputEdges: Edge[] + inputEdges: Edge[], ) => AstNode | Program; export type OnBeforeCompile = ( graph: Graph, engineContext: EngineContext, node: SourceNode, - sibling?: SourceNode + sibling?: SourceNode, ) => Promise | void>; export type ManipulateAst = ( @@ -60,7 +60,7 @@ export type ManipulateAst = ( ast: AstNode | Program, inputEdges: Edge[], node: SourceNode, - sibling: SourceNode + sibling: SourceNode, ) => AstNode | Program; export type NodeParser = { @@ -84,12 +84,12 @@ export type FindInputs = ( ast: Program | AstNode, inputEdges: Edge[], node: SourceNode, - sibling?: SourceNode + sibling?: SourceNode, ) => ComputedInput[]; export type ProduceNodeFiller = ( node: SourceNode, - ast: Program | AstNode + ast: Program | AstNode, ) => Filler; type CoreNodeParser = { @@ -161,7 +161,7 @@ export const coreParsers: CoreParser = { }, findInputs: (engineContext, ast, edges, node, sibling) => { let seen = new Set(); - return node.config.strategies + let inputs = node.config.strategies .flatMap((strategy) => applyStrategy(strategy, ast, node, sibling)) .filter(([input, _]) => { if (!seen.has(input.id)) { @@ -170,6 +170,7 @@ export const coreParsers: CoreParser = { } return false; }); + return inputs; }, produceFiller: (node, ast) => { return (...args) => { @@ -177,10 +178,10 @@ export const coreParsers: CoreParser = { node.sourceType === SourceType.EXPRESSION ? ((ast as Program).program[0] as AstNode) : node.sourceType === SourceType.FN_BODY_FRAGMENT - ? ((ast as Program).program as AstNode[]) - : // Backfilling into the call of this program's filler. - // Similar to texutre2D.ts filler - makeExpression(`${nodeName(node)}(${args.join(', ')})`); + ? ((ast as Program).program as AstNode[]) + : // Backfilling into the call of this program's filler. + // Similar to texutre2D.ts filler + makeExpression(`${nodeName(node)}(${args.join(', ')})`); return fillerNode; }; }, @@ -194,7 +195,7 @@ export const coreParsers: CoreParser = { findInputs: (engineContext, ast, edges, node, sibling) => { return [ ...node.config.strategies.flatMap((strategy) => - applyStrategy(strategy, ast, node, sibling) + applyStrategy(strategy, ast, node, sibling), ), [ nodeInput( @@ -203,13 +204,13 @@ export const coreParsers: CoreParser = { 'filler', 'rgba', ['code'], - false + false, ), (filler) => { const main = findMainOrThrow(ast as Program); main.body.statements = unshiftFnStmtWithIndent( main, - generateFiller(filler()) + generateFiller(filler()), ); return ast; }, @@ -230,7 +231,7 @@ export const coreParsers: CoreParser = { .map((_, index) => alphabet.charAt(index)) .join(` ${node.operator} `) : `a ${node.operator} b`) + - ')' + ')', ); }, findInputs: (engineContext, ast, inputEdges, node, sibling) => { @@ -245,7 +246,7 @@ export const coreParsers: CoreParser = { 'filler', undefined, ['data', 'code'], - false + false, ), (filler) => { let foundPath: Path | undefined; @@ -261,7 +262,7 @@ export const coreParsers: CoreParser = { visit(ast, visitors); if (!foundPath) { throw new Error( - `Im drunk and I think this case is impossible, no "${letter}" found in binary node?` + `Im drunk and I think this case is impossible, no "${letter}" found in binary node?`, ); } @@ -292,7 +293,7 @@ export const coreParsers: CoreParser = { return num / next; } throw new Error( - `Don't know how to evaluate ${operator} for node ${node.name} (${node.id})` + `Don't know how to evaluate ${operator} for node ${node.name} (${node.id})`, ); }); }, diff --git a/src/plugins/three/FrogMaterial.ts b/src/plugins/three/FrogMaterial.ts new file mode 100644 index 0000000..f765737 --- /dev/null +++ b/src/plugins/three/FrogMaterial.ts @@ -0,0 +1,300 @@ +import { replaceFromOffset, replaceLast } from '../../util/string'; +import { + IUniform, + WebGLProgramParametersWithUniforms, + WebGLRenderer, + Material, + Texture, + ShaderChunk, +} from 'three'; + +// Expand Three.js Chunks +export const expandChunks = (glsl: string, depth = 0): string => { + if (depth > 12) return glsl; + return glsl.replace(/#include <(\w+)>/g, (_, chunkName: string) => { + const chunk = (ShaderChunk as Record)[chunkName]; + if (chunk !== undefined) { + return `/* ~~~ #include <${chunkName}> ~~~ */\n${expandChunks( + chunk, + depth + 1, + )}\n/* ~~~ end <${chunkName}> ~~~ */`; + } + return `/* MISSING #include <${chunkName}> */`; + }); +}; + +export type InjectionPoint = { + find: RegExp; + replace: (callExpr: string) => string; + forceProperty?: string; +}; + +// All texture-typed property names across phong / physical / toon in threngine +type InjectableKey = + | 'map' + | 'normalMap' + | 'aoMap' + | 'emissiveMap' + | 'roughnessMap' + | 'specularMap' + | 'displacementMap' + | 'bumpMap' + | 'transmissionMap' + | 'gradientMap' + | 'thickness' + | 'transmission' + | 'position'; + +const INJECTABLE_KEYS: ReadonlySet = new Set([ + 'map', + 'normalMap', + 'aoMap', + 'emissiveMap', + 'roughnessMap', + 'specularMap', + 'displacementMap', + 'bumpMap', + 'transmissionMap', + 'gradientMap', + 'thickness', + 'transmission', + 'position', +]); + +// FrogMaterial properties that replace Three.js geenrated code +const FRAGMENT_INJECTABLE: Partial> = { + map: { + find: /vec4 sampledDiffuseColor = [^;]+;/, + replace: (call: string) => `vec4 sampledDiffuseColor = ${call};`, + forceProperty: 'map', + }, + normalMap: { + find: /vec3 mapN = texture2D\( normalMap, vNormalMapUv \)\.xyz \* 2\.0 - 1\.0;/, + replace: (call: string) => `vec3 mapN = ${call}.rgb * 2.0 - 1.0;`, + forceProperty: 'normalMap', + }, + emissiveMap: { + find: /vec4 emissiveColor = [^;]+;/, + replace: (call: string) => `vec4 emissiveColor = ${call};`, + forceProperty: 'emissiveMap', + }, + roughnessMap: { + find: /vec4 texelRoughness = [^;]+;/, + replace: (call: string) => `vec4 texelRoughness = ${call};`, + forceProperty: 'roughnessMap', + }, + aoMap: { + find: /float ambientOcclusion = [^;]+;/, + replace: (call: string) => + `float ambientOcclusion = ( ${call}.r - 1.0 ) * aoMapIntensity + 1.0;`, + forceProperty: 'aoMap', + }, + specularMap: { + find: /vec4 texelSpecular = [^;]+;/, + replace: (call: string) => `vec4 texelSpecular = ${call};`, + forceProperty: 'specularMap', + }, + thickness: { + find: /material.thickness = [^;]+;/, + replace: (call: string) => `material.thickness = ${call};`, + forceProperty: 'thickness', + }, + transmission: { + find: /material.transmission = [^;]+;/, + replace: (call: string) => `material.transmission = ${call};`, + forceProperty: 'transmission', + }, +}; + +const VERTEX_INJECTABLES: Partial> = { + position: { + find: /vec3 transformed = vec3\( position \);/, + replace: (call: string) => `vec3 transformed = ${call}.xyz;`, + }, + displacementMap: { + find: /texture2D\( displacementMap, vDisplacementMapUv \)/, + replace: (call: string) => call, + forceProperty: 'displacementMap', + }, +}; + +// For injectable properties, also allow a GLSL expression string +type WithInjectables

= { + [K in keyof P]: K extends InjectableKey ? P[K] | string : P[K]; +}; + +export interface ShaderInjection { + search: string | RegExp; + replace: string; +} + +type MaterialConstructor = new (...args: any[]) => Material; + +type ConstructorParams = C extends new ( + params?: infer P, + ...args: any[] +) => Material + ? NonNullable

+ : never; + +type FrogSpecificKeys = + | 'baseMaterial' + | 'fragmentShader' + | 'fragmentOutput' + | 'vertexShader' + | 'vertexOutput' + | 'uniforms' + | 'fragmentInjections' + | 'vertexInjections' + | 'onBeforeCompile'; + +export type FrogMaterialParams< + C extends MaterialConstructor = MaterialConstructor, +> = { + baseMaterial: C; + fragmentShader: string; + fragmentOutput: string; + vertexShader: string; + vertexOutput: string; + uniforms?: Record; + fragmentInjections?: ShaderInjection[]; + vertexInjections?: ShaderInjection[]; + /** Called after all frog transforms are applied to the shader */ + onBeforeCompile?: ( + shader: WebGLProgramParametersWithUniforms, + renderer: WebGLRenderer, + ) => void; +} & Omit>, FrogSpecificKeys>; + +function _create({ + baseMaterial: BaseMaterial, + fragmentShader, + fragmentOutput, + vertexShader, + vertexOutput, + uniforms = {}, + fragmentInjections = [], + vertexInjections = [], + onBeforeCompile: userOnBeforeCompile, + ...baseProps +}: FrogMaterialParams): Material { + // Split baseProps: injectable strings become GLSL injections + dummy textures + const glslInjections: Array<{ find: RegExp; replace: string }> = []; + const materialProps: Record = {}; + + for (const [key, value] of Object.entries( + baseProps as Record, + )) { + if (INJECTABLE_KEYS.has(key) && typeof value === 'string') { + const inj = + FRAGMENT_INJECTABLE[key as InjectableKey] || + VERTEX_INJECTABLES[key as InjectableKey]; + if (inj) { + glslInjections.push({ find: inj.find, replace: inj.replace(value) }); + if (inj.forceProperty) { + materialProps[inj.forceProperty] = new Texture(); + } + } else { + materialProps[key] = new Texture(); + } + } else { + materialProps[key] = value; + } + } + + const mat = new BaseMaterial(materialProps as ConstructorParams); + const engineFnName = `main_${BaseMaterial.name || 'BaseMaterial'}`; + + mat.onBeforeCompile = (shader, renderer) => { + Object.assign(shader.uniforms, uniforms); + mat.userData.shader = shader; + + shader.fragmentShader = expandChunks(shader.fragmentShader); + + shader.fragmentShader = replaceFromOffset( + shader.fragmentShader, + shader.fragmentShader.indexOf('void main'), + /gl_FragColor/g, + 'fragColor', + ); + shader.fragmentShader = replaceLast( + shader.fragmentShader, + '}', + 'return fragColor;\n}', + ); + + shader.fragmentShader = + shader.fragmentShader.replace( + 'void main() {', + `vec4 ${engineFnName}();\n` + + fragmentShader + + `\n\nvec4 ${engineFnName}() {\n vec4 fragColor = vec4(0.0);`, + ) + `\n\nvoid main() { gl_FragColor = ${fragmentOutput}; }`; + + for (const { find, replace } of glslInjections) { + shader.fragmentShader = shader.fragmentShader.replace(find, replace); + } + + for (const { search, replace } of fragmentInjections) { + shader.fragmentShader = shader.fragmentShader.replace(search, replace); + } + + shader.vertexShader = expandChunks(shader.vertexShader); + + shader.vertexShader = replaceFromOffset( + shader.vertexShader, + shader.vertexShader.indexOf('void main'), + 'gl_Position', + 'fragPosition', + ); + shader.vertexShader = replaceLast( + shader.vertexShader, + '}', + 'return fragPosition;\n}', + ); + + shader.vertexShader = + shader.vertexShader.replace( + 'void main() {', + `vec4 ${engineFnName}();\n` + + vertexShader + + `\n\nvec4 ${engineFnName}() {\n vec4 fragPosition = vec4(0.0);`, + ) + `\n\nvoid main() { ${vertexOutput} }`; + + for (const { find, replace } of glslInjections) { + shader.vertexShader = shader.vertexShader.replace(find, replace); + } + + for (const { search, replace } of vertexInjections) { + shader.vertexShader = shader.vertexShader.replace(search, replace); + } + + userOnBeforeCompile?.(shader, renderer); + }; + + mat.customProgramCacheKey = () => + fragmentShader + + '\x00' + + fragmentOutput + + '\x00' + + vertexShader + + '\x00' + + vertexOutput + + '\x00' + + JSON.stringify(fragmentInjections) + + '\x00' + + JSON.stringify(vertexInjections); + + return mat; +} + +// Interface merge: TypeScript sees FrogMaterial as extending THREE.Material, +// so instances can be used wherever THREE.Material is expected. +export interface FrogMaterial< + C extends MaterialConstructor = MaterialConstructor, +> extends Material {} +export class FrogMaterial { + constructor(params: FrogMaterialParams) { + return _create(params) as unknown as FrogMaterial; + } +} diff --git a/src/plugins/three/index.ts b/src/plugins/three/index.ts index 7a87e8c..d8cdbc9 100644 --- a/src/plugins/three/index.ts +++ b/src/plugins/three/index.ts @@ -1,3 +1,9 @@ import { createMaterial, threngine } from './threngine'; export { createMaterial, threngine as engine }; +export { + createFrogMaterialResult, + engineNodeTypeToConstructor, +} from './threngine'; +export { FrogMaterial, expandChunks } from './FrogMaterial'; +export type { FrogMaterialParams } from './FrogMaterial'; diff --git a/src/plugins/three/threngine.test.ts b/src/plugins/three/threngine.test.ts index 419cfde..c6e6b6d 100644 --- a/src/plugins/three/threngine.test.ts +++ b/src/plugins/three/threngine.test.ts @@ -69,7 +69,6 @@ void main() { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, threngine, engineContext); @@ -135,7 +134,6 @@ it('threngine compileSource() linking through vertex', async () => { engine: 'three', nodes: {}, runtime: {}, - debuggingNonsense: {}, }; const result = await compileSource(graph, threngine, engineContext); diff --git a/src/plugins/three/threngine.ts b/src/plugins/three/threngine.ts index 56a0dcc..918ac0d 100644 --- a/src/plugins/three/threngine.ts +++ b/src/plugins/three/threngine.ts @@ -14,8 +14,17 @@ import { Scene, WebGLRenderer, PerspectiveCamera, + DataTexture, + RGBAFormat, } from 'three'; -import { Program } from '@shaderfrog/glsl-parser/ast'; +import { + Program, + FunctionNode, + ExpressionStatementNode, + AssignmentNode, + DeclarationStatementNode, + DeclaratorListNode, +} from '@shaderfrog/glsl-parser/ast'; import { Graph, NodeType, ShaderStage } from '../../graph/graph-types'; import { prepopulatePropertyInputs, mangleMainFn } from '../../graph/graph'; import importers from './importers'; @@ -23,6 +32,19 @@ import importers from './importers'; import { Engine, EngineContext, EngineNodeType } from '../../engine'; import { doesLinkThruShader, CompileResult } from '../../graph/graph'; import { + filterSections, + filterQualifiedStatements, + filterUniformNames, + findShaderSections, + shaderSectionsCons, + LineAndSource, + ShaderSections, + shaderSectionsToProgram, +} from '../../graph/shader-sections'; +import { generate } from '@shaderfrog/glsl-parser'; +import { FrogMaterial, ShaderInjection } from './FrogMaterial'; +import { + makeExpression, returnGlPosition, returnGlPositionHardCoded, returnGlPositionVec3Right, @@ -33,7 +55,7 @@ import { property, SourceNode, } from '../../graph/code-nodes'; -import { NodePosition } from '../../graph/base-node'; +import { nodeInput, NodePosition } from '../../graph/base-node'; import { DataNode, UniformDataType } from '../../graph/data-nodes'; import { namedAttributeStrategy, @@ -110,19 +132,23 @@ export const phongNode = ( property('Bump Scale', 'bumpScale', 'number'), property('Env Map', 'envMap', 'samplerCube'), ], - strategies: [ - uniformStrategy(), - stage === 'fragment' - ? texture2DStrategy() - : namedAttributeStrategy('position'), - ], + strategies: [], }, display: { visibilities: { Uniforms: 'hidden', }, }, - inputs: [], + inputs: [ + nodeInput( + 'Position', + `position`, + 'filler', + undefined, // Data type for what plugs into this filler + ['code', 'data'], + true + ), + ], outputs: [ { name: 'vector4', @@ -231,7 +257,22 @@ export const physicalNode = ( Uniforms: 'hidden', }, }, - inputs: [], + inputs: + // Andy note for migrating: if filler_position is already found on saved + // shaders, found from computedInputs, it will not need to be + // reconstructed / migrated + stage === 'vertex' + ? [ + nodeInput( + 'Position', + `filler_position`, + 'filler', + undefined, // Data type for what plugs into this filler + ['code', 'data'], + true + ), + ] + : [], outputs: [ { name: 'vector4', @@ -244,100 +285,43 @@ export const physicalNode = ( stage, }); -const cacher = ( - engineContext: EngineContext, - graph: Graph, - node: SourceNode, - sibling: SourceNode | undefined, - newValue: (...args: any[]) => any -) => { - const cacheKey = programCacheKey(engineContext, graph, node, sibling); - - if (engineContext.runtime.cache.data[cacheKey]) { - log('Cache hit', cacheKey); - } else { - log('Cache miss', cacheKey); +export const defaultPropertySetting = (property: NodeProperty) => { + if (property.type === 'texture') { + return new Texture(); + } else if (property.type === 'number') { + return 0.5; + } else if (property.type === 'rgb') { + return new Color(1, 1, 1); + } else if (property.type === 'rgba') { + return new Vector4(1, 1, 1, 1); } - const materialData = engineContext.runtime.cache.data[cacheKey] || newValue(); - - // This is nasty: We mutate the runtime context here, and return the partial - // nodeContext later. See also the TODO: Refactor in context.ts. - engineContext.runtime.cache.data[cacheKey] = materialData; - engineContext.runtime.engineMaterial = materialData.material; - - return { - computedSource: - node.stage === 'fragment' ? materialData.fragment : materialData.vertex, - }; }; -const onBeforeCompileMegaShader = ( - engineContext: EngineContext, - newMat: any -) => { - log('compiling three megashader!'); - const { renderer, sceneData, scene, camera } = engineContext.runtime; - const { mesh } = sceneData; - - // Temporarily swap the mesh material to the new one, since materials can - // be mesh specific, render, then get its source code - const originalMaterial = mesh.material; - mesh.material = newMat; - renderer.compile(scene, camera); - - // The references to the compiled shaders in WebGL - const fragmentRef = renderer.properties - .get(mesh.material) - .programs.values() - .next().value.fragmentShader; - const vertexRef = renderer.properties - .get(mesh.material) - .programs.values() - .next().value.vertexShader; - - const gl = renderer.getContext(); - const fragment = gl.getShaderSource(fragmentRef); - const vertex = gl.getShaderSource(vertexRef); - - // Reset the material on the mesh, since the shader we're computing context - // for might not be the one actually want on the mesh - like if a toon node - // was added to the graph but not connected - mesh.material = originalMaterial; - - // Do we even need to do this? This is just for debugging right? Using the - // source on the node is the important thing. - return { - material: newMat, - fragmentRef, - vertexRef, - fragment, - vertex, - }; -}; +const threeMaterialProperties = ( + graph: Graph, + node: SourceNode, + sibling?: SourceNode +): Record => { + // Find inputs to this node that are dependent on a property of the material + const propertyInputs = indexById(node.inputs.filter((i) => i.property)); -const megaShaderMainpulateAst: NodeParser['manipulateAst'] = ( - engineContext, - engine, - graph, - ast, - inputEdges, - node, - sibling -) => { - const programAst = ast as Program; - const mainName = 'main'; // || nodeName(node); - if (node.stage === 'vertex') { - if (doesLinkThruShader(graph, node)) { - returnGlPositionHardCoded(mainName, programAst, 'vec3', 'transformed'); - } else { - returnGlPosition(mainName, programAst); - } - } + // Then look for any edges into those inputs and set the material property + return graph.edges + .filter((edge) => edge.to === node.id || edge.to === sibling?.id) + .reduce>((acc, edge) => { + // Check if we've plugged into an input for a property + const propertyInput = propertyInputs[edge.input]; + if (propertyInput) { + // Find the property itself + const property = (node.config.properties || []).find( + (p) => p.property === propertyInput.property + ) as NodeProperty; - // We specify engine nodes are mangle: false, which is the graph step that - // handles renaming the main fn, so we have to do it ourselves - mangleMainFn(programAst, node, sibling); - return programAst; + // Initialize the property on the material + acc[property.property] = defaultPropertySetting(property); + } + return acc; + }, {}); }; const nodeCacheKey = (graph: Graph, node: SourceNode) => { @@ -351,9 +335,6 @@ const nodeCacheKey = (graph: Graph, node: SourceNode) => { .sort() .join(',') + ']' - // Currently excluding node inputs because these are calculated *after* - // the onbeforecompile, so the next compile, they'll all change! - // node.inputs.map((i) => `${i.id}${i.bakeable}`) ); }; @@ -363,9 +344,6 @@ const programCacheKey = ( node: SourceNode, sibling?: SourceNode ) => { - // The megashader source is dependent on scene information, like the number - // and type of lights in the scene. This kinda sucks - it's duplicating - // three's material cache key, and is coupled to how three builds shaders const { scene } = engineContext.runtime; const lights: string[] = []; scene.traverse((obj: any) => { @@ -389,43 +367,74 @@ const programCacheKey = ( ); }; -export const defaultPropertySetting = (property: NodeProperty) => { - if (property.type === 'texture') { - return new Texture(); - } else if (property.type === 'number') { - return 0.5; - } else if (property.type === 'rgb') { - return new Color(1, 1, 1); - } else if (property.type === 'rgba') { - return new Vector4(1, 1, 1, 1); - } +const onBeforeCompileMegaShader = ( + engineContext: EngineContext, + newMat: any +) => { + log('Compiling three megashader!'); + const { renderer, sceneData, scene, camera } = engineContext.runtime; + const { mesh } = sceneData; + + // Mirror the defines that FrogMaterial always sets so the force-compiled + // megashader includes the same conditional declarations (e.g. tangent, vUv). + newMat.defines = { + ...(newMat.defines || {}), + USE_UV: '', + USE_UV2: '', + USE_TANGENT: '', + }; + + const originalMaterial = mesh.material; + mesh.material = newMat; + renderer.compile(scene, camera); + + const fragmentRef = renderer.properties + .get(mesh.material) + .programs.values() + .next().value.fragmentShader; + const vertexRef = renderer.properties + .get(mesh.material) + .programs.values() + .next().value.vertexShader; + + const gl = renderer.getContext(); + const fragment = gl.getShaderSource(fragmentRef); + const vertex = gl.getShaderSource(vertexRef); + + mesh.material = originalMaterial; + + return { + material: newMat, + fragmentRef, + vertexRef, + fragment, + vertex, + }; }; -const threeMaterialProperties = ( +const cacher = ( + engineContext: EngineContext, graph: Graph, node: SourceNode, - sibling?: SourceNode -): Record => { - // Find inputs to this node that are dependent on a property of the material - const propertyInputs = indexById(node.inputs.filter((i) => i.property)); + sibling: SourceNode | undefined, + newValue: (...args: any[]) => any +) => { + const cacheKey = programCacheKey(engineContext, graph, node, sibling); - // Then look for any edges into those inputs and set the material property - return graph.edges - .filter((edge) => edge.to === node.id || edge.to === sibling?.id) - .reduce>((acc, edge) => { - // Check if we've plugged into an input for a property - const propertyInput = propertyInputs[edge.input]; - if (propertyInput) { - // Find the property itself - const property = (node.config.properties || []).find( - (p) => p.property === propertyInput.property - ) as NodeProperty; + if (engineContext.runtime.cache.data[cacheKey]) { + log('Cache hit', cacheKey); + } else { + log('Cache miss', cacheKey); + } + const materialData = engineContext.runtime.cache.data[cacheKey] || newValue(); - // Initialize the property on the material - acc[property.property] = defaultPropertySetting(property); - } - return acc; - }, {}); + engineContext.runtime.cache.data[cacheKey] = materialData; + engineContext.runtime.engineMaterial = materialData.material; + + return { + computedSource: + node.stage === 'fragment' ? materialData.fragment : materialData.vertex, + }; }; export type ThreeRuntime = { @@ -550,12 +559,7 @@ export const toonNode = ( property('Env Map', 'envMap', 'samplerCube'), property('Env Map Intensity', 'envMapIntensity', 'number'), ], - strategies: [ - uniformStrategy(), - stage === 'fragment' - ? texture2DStrategy() - : namedAttributeStrategy('position'), - ], + strategies: [], }, display: { visibilities: { @@ -704,7 +708,10 @@ export const threngine: Engine = { }) ) ), - manipulateAst: megaShaderMainpulateAst, + produceFiller: + (_node, _ast) => + (...args: string[]) => + makeExpression(`main_MeshPhongMaterial(${args.join(', ')})`), }, [EngineNodeType.physical]: { onBeforeCompile: async (graph, engineContext, node, sibling) => @@ -712,15 +719,15 @@ export const threngine: Engine = { onBeforeCompileMegaShader( engineContext, new MeshPhysicalMaterial({ - // These properties are copied onto the runtime RawShaderMaterial. - // These exist on the MeshPhysicalMaterial but only in the - // prototype. We have to hard code them for Object.keys() to work ...node.config.hardCodedProperties, ...threeMaterialProperties(graph, node, sibling), }) ) ), - manipulateAst: megaShaderMainpulateAst, + produceFiller: + (_node, _ast) => + (...args: string[]) => + makeExpression(`main_MeshPhysicalMaterial(${args.join(', ')})`), }, [EngineNodeType.toon]: { onBeforeCompile: async (graph, engineContext, node, sibling) => @@ -735,7 +742,10 @@ export const threngine: Engine = { }) ) ), - manipulateAst: megaShaderMainpulateAst, + produceFiller: + (_node, _ast) => + (...args: string[]) => + makeExpression(`main_MeshToonMaterial(${args.join(', ')})`), }, }, }; @@ -785,6 +795,10 @@ export const createMaterial = ( property !== 'type' && // "precision" adds a precision preprocessor line property !== 'precision' && + // For debugging, these pull in the frogmaterial set properties, which + // then messes up the rawshadermaterial + property !== 'onBeforeCompile' && + property !== 'userData' && // Ignore existing properties !(property in initialProperties) && // Ignore STANDARD and PHYSICAL defines to the top of the shader in @@ -812,3 +826,279 @@ export const createMaterial = ( return material; }; + +export const engineNodeTypeToConstructor = (type: string) => { + if (type === EngineNodeType.physical) return MeshPhysicalMaterial; + if (type === EngineNodeType.phong) return MeshPhongMaterial; + if (type === EngineNodeType.toon) return MeshToonMaterial; + return null; +}; + +const frogMergeOptions = { includePrecisions: false, includeVersion: false }; + +const getStructTypeName = ( + stmt: DeclarationStatementNode +): string | undefined => { + try { + const specifier = (stmt.declaration as any).specified_type?.specifier + ?.specifier; + return specifier?.typeName?.identifier; + } catch { + return undefined; + } +}; + +const collectThreeNames = ( + sections: ShaderSections +): { uniforms: Set; qualified: Set; structs: Set } => { + const uniforms = new Set(); + const qualified = new Set(); + const structs = new Set(); + + for (const line of sections.uniforms) { + const decl = line.source.declaration; + if (decl.type === 'interface_declarator') { + const id = (decl as any).identifier?.identifier?.identifier; + if (id) uniforms.add(id); + } else if (decl.type === 'declarator_list') { + (decl as DeclaratorListNode).declarations?.forEach((d) => + uniforms.add(d.identifier.identifier) + ); + } + } + + for (const line of [...sections.inStatements, ...sections.outStatements]) { + const dec = line.source.declaration as DeclaratorListNode; + dec.declarations?.forEach((d) => qualified.add(d.identifier.identifier)); + } + + for (const line of sections.structs) { + const name = getStructTypeName(line.source); + if (name) structs.add(name); + } + + return { uniforms, qualified, structs }; +}; + +// Strip top-level declarations that the Three.js megashader already provides, +// so injected user GLSL doesn't redefine them. Uses the parsed megashader +// sections from the engine node context rather than a hardcoded list. +const stripThreeDeclarations = ( + sections: ShaderSections, + threeShaderSections: ShaderSections +): ShaderSections => { + const { + uniforms: threeUniforms, + qualified: threeQualified, + structs: threeStructs, + } = collectThreeNames(threeShaderSections); + + return { + ...sections, + inStatements: filterQualifiedStatements( + sections.inStatements, + (name) => !threeQualified.has(name) + ), + outStatements: filterQualifiedStatements( + sections.outStatements, + (name) => !threeQualified.has(name) + ), + uniforms: filterUniformNames( + sections.uniforms, + (name) => !threeUniforms.has(name) + ), + structs: sections.structs.filter( + (s) => !threeStructs.has(getStructTypeName(s.source) ?? '') + ), + }; +}; + +// Extract the right-hand side of `assignTarget = ` from the output +// node's compiled main function. Works for both code nodes (which produce +// `frogFragOut = main_NodeName()`) and expression nodes (which produce +// `frogFragOut = inlined_expression`). +const extractOutputExpr = ( + sections: ShaderSections, + outputNodeId: string, + assignTarget: string, + fallback: string +): string => { + const entry = sections.program.find((s) => s.nodeId === outputNodeId); + if (!entry) return fallback; + const fn = entry.source as FunctionNode; + if (fn.type !== 'function') return fallback; + const body = fn.body; + for (const stmt of body.statements) { + const es = stmt as ExpressionStatementNode; + if (es.type !== 'expression_statement') continue; + const assign = es.expression as AssignmentNode; + if (assign.type !== 'assignment') continue; + const left = assign.left as any; + if (left?.identifier !== assignTarget) continue; + return generate(assign.right); + } + return fallback; +}; + +// Extract all statements from the output node's main() body. The +// MAGIC_OUTPUT_STMTS filler prepends orphan vertex main() calls before the +// gl_Position assignment; extractOutputExpr only grabs the RHS and drops them. +const extractVertexMainStmts = ( + sections: ShaderSections, + outputNodeId: string, + fallback: string +): string => { + const entry = sections.program.find((s) => s.nodeId === outputNodeId); + if (!entry) return `gl_Position = ${fallback};`; + const fn = entry.source as FunctionNode; + if (fn.type !== 'function') return `gl_Position = ${fallback};`; + return fn.body.statements.map((stmt) => generate(stmt)).join('\n '); +}; + +export const createFrogMaterialResult = ( + compileResult: CompileResult, + ctx: EngineContext, + graph: Graph +) => { + const { compileResult: graphResult } = compileResult; + + const engineNodeIds = new Set( + graph.nodes.filter((node) => (node as CodeNode).engine).map(({ id }) => id) + ); + + const engineNode = graph.nodes.find((n) => engineNodeIds.has(n.id)); + + if (!engineNode) { + return createMaterial(compileResult, ctx); + } + + const BaseMaterial = engineNodeTypeToConstructor(engineNode.type); + + if (!BaseMaterial) { + return createMaterial(compileResult, ctx); + } + + // Filter out engine node and output node sections — their GLSL comes from + // Three.js and the output wrapper, neither of which should be injected + const skipIds = new Set([ + graphResult.outputFrag.id, + graphResult.outputVert.id, + ]); + const noSkip = (s: LineAndSource) => !skipIds.has(s.nodeId); + + // Get parsed Three.js megashader sections from engine node contexts so we + // can dynamically strip whatever Three.js actually declares in this compile. + // Must use compileResult.updatedNodeContext (not ctx.nodes) since the engine + // node's megashader AST is computed during this compilation pass. + const { updatedNodeContext } = compileResult; + const engineNodes = Array.from(engineNodeIds).map( + (id) => graph.nodes.find((n) => n.id === id) as CodeNode + ); + const threeFragSections = (() => { + const fragNode = engineNodes.find((n) => n?.stage === 'fragment'); + const ast = fragNode + ? (updatedNodeContext[fragNode.id]?.ast as Program) + : null; + return ast?.program?.length + ? findShaderSections('three', ast) + : shaderSectionsCons(); + })(); + const threeVertSections = (() => { + const vertNode = engineNodes.find((n) => n?.stage === 'vertex'); + const ast = vertNode + ? (updatedNodeContext[vertNode.id]?.ast as Program) + : null; + return ast?.program?.length + ? findShaderSections('three', ast) + : shaderSectionsCons(); + })(); + + const fragmentShader = generate( + shaderSectionsToProgram( + stripThreeDeclarations( + filterSections(noSkip, graphResult.fragment), + // graphResult.fragment, + threeFragSections + ), + frogMergeOptions + ).program + ); + const vertexShader = generate( + shaderSectionsToProgram( + stripThreeDeclarations( + filterSections(noSkip, graphResult.vertex), + // graphResult.vertex, + threeVertSections + ), + frogMergeOptions + ).program + ); + + // Extract the final output expressions from the output nodes' compiled AST. + // This handles both code nodes (frogFragOut = main_NodeName()) and expression + // nodes like add (frogFragOut = inlined_expr) without guessing the function name. + const fragmentOutput = extractOutputExpr( + graphResult.fragment, + graphResult.outputFrag.id, + 'frogFragOut', + 'vec4(1.0)' + ); + const vertexOutput = extractVertexMainStmts( + graphResult.vertex, + graphResult.outputVert.id, + 'vec4(1.0)' + ); + + const uniforms: Record = { + time: { value: 0 }, + cameraPosition: { value: new Vector3(1.0) }, + renderResolution: { value: new Vector2(1.0) }, + }; + + let fragmentInjections: ShaderInjection[] = []; + let vertexInjections: ShaderInjection[] = []; + + const additionalProperties = Object.entries( + compileResult.compileResult.engineNodeProperties + ).reduce>((acc, [name, property]) => { + if ( + property.fillerGroup.filler + .toString() + .includes('strategy_type_assignmentTo') + ) { + fragmentInjections.push({ + search: new RegExp(`(${name} = ).+;`), + replace: `$1${property.result.toString()};`, + }); + vertexInjections.push({ + search: new RegExp(`(${name} = ).+;`), + replace: `$1${property.result.toString()};`, + }); + } else { + acc[name] = property.result; + } + return acc; + }, {}); + + const mat = new FrogMaterial({ + baseMaterial: BaseMaterial as any, + fragmentShader, + fragmentOutput, + vertexShader, + vertexOutput, + uniforms, + fragmentInjections, + vertexInjections, + ...additionalProperties, + }); + + // Three has switched to vMapUv / vNormalMapUv / vBumpMapUv etc. Most legacy + // ShaderFrog shaders depend on uv. + const m = mat as any; + m.defines = m.defines ?? {}; + m.defines.USE_UV = ''; + m.defines.USE_UV2 = ''; + m.defines.USE_TANGENT = ''; + + return mat; +}; diff --git a/src/strategy/assignmentTo.ts b/src/strategy/assignmentTo.ts index 1ee9819..b0049ea 100644 --- a/src/strategy/assignmentTo.ts +++ b/src/strategy/assignmentTo.ts @@ -2,6 +2,7 @@ import { findAssignmentTo } from '../util/ast'; import { nodeInput } from '../graph/base-node'; import { BaseStrategy, ApplyStrategy, StrategyType } from '.'; import { AssignmentNode, AstNode } from '@shaderfrog/glsl-parser/ast'; +import { SourceNode } from '../graph'; export interface AssignmentToStrategy extends BaseStrategy { type: StrategyType.ASSIGNMENT_TO; @@ -27,11 +28,15 @@ export const applyAssignmentToStrategy: ApplyStrategy = ( graphNode, siblingNode ) => { - const assignNode = findAssignmentTo( - ast, - strategy.config.assignTo, - strategy.config.nth || 1 - ); + // Hard code an input for engines. TODO: Woudl probably be nice to hard code + // the input regardless of if an assignment is found! + const assignNode = (graphNode as SourceNode).engine + ? { + type: 'expression_statement', + expression: '', + semi: ';', + } + : findAssignmentTo(ast, strategy.config.assignTo, strategy.config.nth || 1); const name = strategy.config.assignTo; return assignNode @@ -46,6 +51,8 @@ export const applyAssignmentToStrategy: ApplyStrategy = ( false ), (filler) => { + // Allow engines to know what type of filler this is + 'strategy_type_assignmentTo'; const filled = filler(); if ('expression' in assignNode) { (assignNode.expression as AssignmentNode).right = diff --git a/src/util/string.ts b/src/util/string.ts new file mode 100644 index 0000000..c03af54 --- /dev/null +++ b/src/util/string.ts @@ -0,0 +1,18 @@ +export const replaceFromOffset = ( + str: string, + offset: number, + search: string | RegExp, + replacement: string +) => str.slice(0, offset) + str.slice(offset).replace(search, replacement); + +export const replaceLast = ( + str: string, + charToReplace: string, + replacement: string +) => { + const index = str.lastIndexOf(charToReplace); + if (index === -1) return str; + return ( + str.slice(0, index) + replacement + str.slice(index + charToReplace.length) + ); +}; diff --git a/tsconfig.json b/tsconfig.json index a7f602d..f3bdaa7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "baseUrl": ".", "target": "es5", - "lib": ["esnext"], + "lib": ["esnext", "dom"], "strict": true, "allowJs": true, "skipLibCheck": true, @@ -18,4 +18,4 @@ }, "include": ["./src/**/*.ts"], "exclude": ["node_modules"] -} +} \ No newline at end of file