Cleanup
This commit is contained in:
@@ -104,10 +104,10 @@ subscriptions after a scene transition.
|
||||
|
||||
## Proposed package shape
|
||||
|
||||
Start in this workspace as `packages/effect-platform-godot` under the internal
|
||||
scope `@codename-painless/effect-platform-godot`. Keep it independently
|
||||
publishable; an unscoped `effect-platform-godot` package name or the
|
||||
`@effect/*` namespace should not be assumed available.
|
||||
Start in this workspace as `packages/effect-platform-godot` under the
|
||||
`@effect-godot/effect-platform-godot` scope. Keep it independently
|
||||
publishable; an unscoped `effect-platform-godot` package name should not be
|
||||
assumed available.
|
||||
|
||||
```text
|
||||
packages/effect-platform-godot/
|
||||
|
||||
@@ -3,40 +3,24 @@
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "effect-godot",
|
||||
"name": "@effect-godot/monorepo",
|
||||
"devDependencies": {
|
||||
"typescript": "^7.0.2",
|
||||
},
|
||||
},
|
||||
"packages/common": {
|
||||
"name": "@codename-painless/common",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"effect": "^4.0.0-rc.112",
|
||||
},
|
||||
},
|
||||
"packages/effect-godot": {
|
||||
"name": "@codename-painless/effect-godot",
|
||||
"name": "effect-godot",
|
||||
"version": "0.0.0",
|
||||
},
|
||||
"packages/game": {
|
||||
"name": "@codename-painless/game",
|
||||
"name": "@effect-godot/game",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@codename-painless/common": "workspace:*",
|
||||
"effect": "^4.0.0-rc.112",
|
||||
"mitt": "^3.0.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codename-painless/godot-bin": "workspace:*",
|
||||
"@effect/platform-bun": "^4.0.0-rc.112",
|
||||
"@types/node": "^26.4.1",
|
||||
"esbuild": "^0.28.2",
|
||||
"rolldown": "^1.2.7",
|
||||
"@effect-godot/godot-bin": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/godot-bin": {
|
||||
"name": "@codename-painless/godot-bin",
|
||||
"name": "@effect-godot/godot-bin",
|
||||
"version": "0.0.0",
|
||||
"bin": {
|
||||
"godot-bin": "./src/cli.ts",
|
||||
@@ -48,70 +32,14 @@
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@codename-painless/common": ["@codename-painless/common@workspace:packages/common"],
|
||||
"@effect-godot/game": ["@effect-godot/game@workspace:packages/game"],
|
||||
|
||||
"@codename-painless/effect-godot": ["@codename-painless/effect-godot@workspace:packages/effect-godot"],
|
||||
|
||||
"@codename-painless/game": ["@codename-painless/game@workspace:packages/game"],
|
||||
|
||||
"@codename-painless/godot-bin": ["@codename-painless/godot-bin@workspace:packages/godot-bin"],
|
||||
"@effect-godot/godot-bin": ["@effect-godot/godot-bin@workspace:packages/godot-bin"],
|
||||
|
||||
"@effect/platform-bun": ["@effect/platform-bun@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-Y5n2HhV/vsbrJls9ukX42RL7zqXQxISHegWFsP9njsCyDmGTnq7QYSO82rglwrLPwtTrv5dkSRTuGw4+oXxgMg=="],
|
||||
|
||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
|
||||
@@ -124,40 +52,6 @@
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.148.0", "", {}, "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A=="],
|
||||
|
||||
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.7", "", { "os": "android", "cpu": "arm64" }, "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.7", "", { "os": "linux", "cpu": "arm" }, "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.7", "", { "os": "none", "cpu": "arm64" }, "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
@@ -206,12 +100,10 @@
|
||||
|
||||
"effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="],
|
||||
|
||||
"esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
|
||||
"effect-godot": ["effect-godot@workspace:packages/effect-godot"],
|
||||
|
||||
"fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="],
|
||||
|
||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||
|
||||
"msgpackr": ["msgpackr@2.1.0", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ=="],
|
||||
|
||||
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
|
||||
@@ -220,8 +112,6 @@
|
||||
|
||||
"pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="],
|
||||
|
||||
"rolldown": ["rolldown@1.2.7", "", { "dependencies": { "@oxc-project/types": "=0.148.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.7", "@rolldown/binding-android-arm64": "1.2.7", "@rolldown/binding-darwin-arm64": "1.2.7", "@rolldown/binding-darwin-x64": "1.2.7", "@rolldown/binding-freebsd-x64": "1.2.7", "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", "@rolldown/binding-linux-arm64-gnu": "1.2.7", "@rolldown/binding-linux-arm64-musl": "1.2.7", "@rolldown/binding-linux-ppc64-gnu": "1.2.7", "@rolldown/binding-linux-s390x-gnu": "1.2.7", "@rolldown/binding-linux-x64-gnu": "1.2.7", "@rolldown/binding-linux-x64-musl": "1.2.7", "@rolldown/binding-openharmony-arm64": "1.2.7", "@rolldown/binding-win32-arm64-msvc": "1.2.7", "@rolldown/binding-win32-x64-msvc": "1.2.7" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig=="],
|
||||
|
||||
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
||||
|
||||
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||
|
||||
+2
-6
@@ -1,14 +1,10 @@
|
||||
{
|
||||
"name": "effect-godot",
|
||||
"name": "@effect-godot/monorepo",
|
||||
"private": true,
|
||||
"workspaces": ["packages/*"],
|
||||
"scripts": {
|
||||
"build": "bun run --cwd packages/game build",
|
||||
"dev": "bun run --cwd packages/game dev",
|
||||
"typegen": "bun run --cwd packages/game typegen",
|
||||
"edit": "bun run --cwd packages/game edit",
|
||||
"run": "bun run --cwd packages/game run",
|
||||
"cef:fetch": "bun run --cwd packages/game cef:fetch"
|
||||
"run": "bun run --cwd packages/game run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^7.0.2"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@codename-painless/common",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"effect": "^4.0.0-rc.112"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// The events the game can push to the UI, over RpcClient/RpcServer's
|
||||
// pseudo-RPC engine. Unlike procedures these are unsolicited broadcasts:
|
||||
// nothing correlates them to a request.
|
||||
//
|
||||
// Empty for now — no events defined yet.
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Event = Schema.TaggedUnion({})
|
||||
export type Event = typeof Event.Type
|
||||
@@ -1,14 +0,0 @@
|
||||
// The procedures the UI can trigger on the game, over RpcClient/RpcServer's
|
||||
// pseudo-RPC engine. Each tag pairs a request shape with the success/error
|
||||
// schema its response is decoded against.
|
||||
//
|
||||
// Empty for now — no procedures defined yet.
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Request = Schema.TaggedUnion({})
|
||||
export type Request = typeof Request.Type
|
||||
|
||||
export const success = {} satisfies Record<Request["_tag"], Schema.Top>
|
||||
|
||||
export const error = {} satisfies Record<Request["_tag"], Schema.Top>
|
||||
@@ -1,119 +0,0 @@
|
||||
// The UI side of the pseudo-RPC engine: call the game's procedures and
|
||||
// subscribe to its events. See RpcMessage.ts for the wire protocol.
|
||||
|
||||
import { Effect, PubSub, Schema, Scope, Stream } from "effect"
|
||||
import * as RpcMessage from "./RpcMessage"
|
||||
|
||||
export interface Client<
|
||||
Req extends RpcMessage.AnyRequestUnion,
|
||||
Success extends RpcMessage.SchemaByTag<Req>,
|
||||
Err extends RpcMessage.SchemaByTag<Req>,
|
||||
Ev extends RpcMessage.AnySchema,
|
||||
> {
|
||||
readonly call: <T extends RpcMessage.Tag<Req>>(
|
||||
request: RpcMessage.RequestOf<Req, T>,
|
||||
) => Effect.Effect<Success[T]["Type"], Err[T]["Type"]>
|
||||
readonly events: Stream.Stream<Ev["Type"]>
|
||||
}
|
||||
|
||||
// Scoped: holds a live transport subscription and an event PubSub for as
|
||||
// long as the returned Client is in use, and tears both down when the scope
|
||||
// closes.
|
||||
export function make<
|
||||
Req extends RpcMessage.AnyRequestUnion,
|
||||
Success extends RpcMessage.SchemaByTag<Req>,
|
||||
Err extends RpcMessage.SchemaByTag<Req>,
|
||||
Ev extends RpcMessage.AnySchema,
|
||||
>(options: {
|
||||
readonly transport: RpcMessage.Transport
|
||||
readonly request: Req
|
||||
readonly success: Success
|
||||
readonly error: Err
|
||||
readonly event: Ev
|
||||
}): Effect.Effect<Client<Req, Success, Err, Ev>, never, Scope.Scope> {
|
||||
return Effect.gen(function* () {
|
||||
const pending = new Map<
|
||||
string,
|
||||
(envelope: RpcMessage.ResponseEnvelope) => void
|
||||
>()
|
||||
const pubsub = yield* PubSub.unbounded<Ev["Type"]>()
|
||||
yield* Effect.addFinalizer(() => PubSub.shutdown(pubsub))
|
||||
|
||||
const unsubscribe = options.transport.onMessage((raw) => {
|
||||
const parsed = RpcMessage.parseQuietly(raw)
|
||||
if (parsed === undefined) return
|
||||
const envelope = RpcMessage.decodeQuietly(
|
||||
RpcMessage.GameToUiEnvelope,
|
||||
parsed,
|
||||
"game -> ui message",
|
||||
)
|
||||
if (!envelope) return
|
||||
|
||||
if (envelope._tag === "RpcEvent") {
|
||||
const event = RpcMessage.decodeQuietly(
|
||||
options.event,
|
||||
envelope.event,
|
||||
"event",
|
||||
)
|
||||
if (event === undefined) return
|
||||
PubSub.publishUnsafe(pubsub, event)
|
||||
return
|
||||
}
|
||||
|
||||
const resolve = pending.get(envelope.id)
|
||||
if (!resolve) return
|
||||
pending.delete(envelope.id)
|
||||
resolve(envelope)
|
||||
})
|
||||
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
|
||||
|
||||
function call<T extends RpcMessage.Tag<Req>>(
|
||||
request: RpcMessage.RequestOf<Req, T>,
|
||||
): Effect.Effect<Success[T]["Type"], Err[T]["Type"]> {
|
||||
return Effect.callback<unknown, unknown>((resume) => {
|
||||
const tag = (request as { _tag: string })
|
||||
._tag as RpcMessage.Tag<Req>
|
||||
const id = RpcMessage.nextId()
|
||||
pending.set(id, (envelope) => {
|
||||
if (envelope._tag === "RpcSuccess") {
|
||||
const value = RpcMessage.decodeQuietly(
|
||||
options.success[tag],
|
||||
envelope.value,
|
||||
`${tag} result`,
|
||||
)
|
||||
resume(
|
||||
value === undefined
|
||||
? Effect.die(
|
||||
`[rpc] malformed result for "${tag}"`,
|
||||
)
|
||||
: Effect.succeed(value),
|
||||
)
|
||||
} else {
|
||||
const error = RpcMessage.decodeQuietly(
|
||||
options.error[tag],
|
||||
envelope.error,
|
||||
`${tag} error`,
|
||||
)
|
||||
resume(
|
||||
error === undefined
|
||||
? Effect.die(
|
||||
`[rpc] malformed error for "${tag}"`,
|
||||
)
|
||||
: Effect.fail(error),
|
||||
)
|
||||
}
|
||||
})
|
||||
const encoded = Schema.encodeSync(options.request)(request)
|
||||
options.transport.send(
|
||||
JSON.stringify({
|
||||
_tag: "RpcRequest",
|
||||
id,
|
||||
request: encoded,
|
||||
}),
|
||||
)
|
||||
}) as Effect.Effect<Success[T]["Type"], Err[T]["Type"]>
|
||||
}
|
||||
|
||||
return { call, events: Stream.fromPubSub(pubsub) }
|
||||
})
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// The wire protocol RpcClient.ts and RpcServer.ts both speak, and the
|
||||
// transport contract either side plugs into it.
|
||||
//
|
||||
// godot-cef only gives two one-way string channels: the UI pushes a string to
|
||||
// the game (`window.sendIpcMessage` / the `ipc_message` signal) and the game
|
||||
// pushes a string to the UI (`CefTexture.eval` running JS in the page). There
|
||||
// is no real request/response transport underneath. RpcClient/RpcServer
|
||||
// emulate one on top of those two channels: every UI -> game request carries
|
||||
// an `id`, the matching game -> UI reply is correlated by that same `id`, and
|
||||
// anything the game pushes without a request (a broadcast) is delivered as
|
||||
// an event instead. Hence "pseudo": the RPC semantics are real, the
|
||||
// transport is not.
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
// Both Godot's JS engine and the browser provide `console`; this package's
|
||||
// tsconfig otherwise stays deliberately free of DOM/Node lib globals.
|
||||
declare const console: {
|
||||
readonly log: (...args: ReadonlyArray<unknown>) => void
|
||||
}
|
||||
|
||||
// The only thing either runtime has to supply — a way to send a string out
|
||||
// and a way to be notified when one arrives.
|
||||
export interface Transport {
|
||||
readonly send: (message: string) => void
|
||||
readonly onMessage: (listener: (message: string) => void) => () => void
|
||||
}
|
||||
|
||||
// The schemas plugged into RpcClient/RpcServer never need decoding/encoding
|
||||
// services (they're plain JSON-shaped data), which is what lets a single
|
||||
// schema value be used for both `decodeUnknownSync` and `encodeSync`.
|
||||
export type AnySchema = Schema.Codec<unknown, unknown, never, never>
|
||||
|
||||
export const RequestEnvelope = Schema.TaggedStruct("RpcRequest", {
|
||||
id: Schema.String,
|
||||
request: Schema.Unknown,
|
||||
})
|
||||
|
||||
export const ResponseEnvelope = Schema.TaggedUnion({
|
||||
RpcSuccess: { id: Schema.String, value: Schema.Unknown },
|
||||
RpcFailure: { id: Schema.String, error: Schema.Unknown },
|
||||
})
|
||||
export type ResponseEnvelope = typeof ResponseEnvelope.Type
|
||||
|
||||
export const EventEnvelope = Schema.TaggedStruct("RpcEvent", {
|
||||
event: Schema.Unknown,
|
||||
})
|
||||
|
||||
export const GameToUiEnvelope = Schema.Union([ResponseEnvelope, EventEnvelope])
|
||||
|
||||
export function nextId(): string {
|
||||
return Math.random().toString(36).slice(2)
|
||||
}
|
||||
|
||||
export function decodeQuietly<S extends AnySchema>(
|
||||
schema: S,
|
||||
input: unknown,
|
||||
what: string,
|
||||
): S["Type"] | undefined {
|
||||
try {
|
||||
return Schema.decodeUnknownSync(schema)(input)
|
||||
} catch (cause) {
|
||||
console.log(`[rpc] ignoring malformed ${what}`, cause)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function parseQuietly(raw: string): unknown {
|
||||
try {
|
||||
return JSON.parse(raw)
|
||||
} catch (cause) {
|
||||
console.log("[rpc] ignoring malformed message", raw, cause)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// A tagged union of requests, as produced by `Schema.TaggedUnion`. Every
|
||||
// procedure a group exposes is one member of this union.
|
||||
export type AnyRequestUnion = AnySchema & {
|
||||
readonly Type: { readonly _tag: string }
|
||||
readonly match: (
|
||||
value: any,
|
||||
cases: Record<string, (value: any) => unknown>,
|
||||
) => unknown
|
||||
}
|
||||
|
||||
export type Tag<Req extends AnyRequestUnion> = Req["Type"]["_tag"]
|
||||
export type RequestOf<
|
||||
Req extends AnyRequestUnion,
|
||||
T extends Tag<Req>,
|
||||
> = Extract<Req["Type"], { readonly _tag: T }>
|
||||
export type SchemaByTag<Req extends AnyRequestUnion> = {
|
||||
readonly [T in Tag<Req>]: AnySchema
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
// The game side of the pseudo-RPC engine: handle the UI's procedure calls
|
||||
// and push events to it. See RpcMessage.ts for the wire protocol.
|
||||
|
||||
import { Effect, Exit, Option, Schema, Scope } from "effect"
|
||||
import * as RpcMessage from "./RpcMessage"
|
||||
|
||||
// Both Godot's JS engine and the browser provide `console`; this package's
|
||||
// tsconfig otherwise stays deliberately free of DOM/Node lib globals.
|
||||
declare const console: {
|
||||
readonly log: (...args: ReadonlyArray<unknown>) => void
|
||||
}
|
||||
|
||||
export interface Server<Ev extends RpcMessage.AnySchema> {
|
||||
readonly emit: (event: Ev["Type"]) => Effect.Effect<void>
|
||||
}
|
||||
|
||||
// Scoped: holds a live transport subscription for as long as the returned
|
||||
// Server is in use, and tears it down when the scope closes.
|
||||
export function make<
|
||||
Req extends RpcMessage.AnyRequestUnion,
|
||||
Success extends RpcMessage.SchemaByTag<Req>,
|
||||
Err extends RpcMessage.SchemaByTag<Req>,
|
||||
Ev extends RpcMessage.AnySchema,
|
||||
>(options: {
|
||||
readonly transport: RpcMessage.Transport
|
||||
readonly request: Req
|
||||
readonly success: Success
|
||||
readonly error: Err
|
||||
readonly event: Ev
|
||||
readonly handlers: {
|
||||
readonly [T in RpcMessage.Tag<Req>]: (
|
||||
request: RpcMessage.RequestOf<Req, T>,
|
||||
) => Effect.Effect<Success[T]["Type"], Err[T]["Type"]>
|
||||
}
|
||||
}): Effect.Effect<Server<Ev>, never, Scope.Scope> {
|
||||
return Effect.gen(function* () {
|
||||
const unsubscribe = options.transport.onMessage((raw) => {
|
||||
const parsed = RpcMessage.parseQuietly(raw)
|
||||
if (parsed === undefined) return
|
||||
const envelope = RpcMessage.decodeQuietly(
|
||||
RpcMessage.RequestEnvelope,
|
||||
parsed,
|
||||
"ui -> game message",
|
||||
)
|
||||
if (!envelope) return
|
||||
const request = RpcMessage.decodeQuietly(
|
||||
options.request,
|
||||
envelope.request,
|
||||
"request",
|
||||
)
|
||||
if (!request) return
|
||||
|
||||
const tag = (request as { _tag: string })
|
||||
._tag as RpcMessage.Tag<Req>
|
||||
const effect = options.request.match(
|
||||
request,
|
||||
options.handlers as Record<
|
||||
string,
|
||||
(value: unknown) => Effect.Effect<unknown, unknown>
|
||||
>,
|
||||
) as Effect.Effect<unknown, unknown>
|
||||
|
||||
Effect.runPromiseExit(effect).then((exit) => {
|
||||
if (Exit.isSuccess(exit)) {
|
||||
const value = Schema.encodeSync(options.success[tag])(
|
||||
exit.value,
|
||||
)
|
||||
options.transport.send(
|
||||
JSON.stringify({
|
||||
_tag: "RpcSuccess",
|
||||
id: envelope.id,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// A defect here is a genuine bug (the handler threw instead of
|
||||
// failing with its declared error type) — there's no schema to
|
||||
// encode it as, so it's logged loudly and the call is left pending
|
||||
// rather than pretending it succeeded.
|
||||
const failure = Exit.findErrorOption(exit)
|
||||
if (Option.isNone(failure)) {
|
||||
console.log(
|
||||
`[rpc] unhandled defect in "${tag}" handler`,
|
||||
exit.cause,
|
||||
)
|
||||
return
|
||||
}
|
||||
const error = Schema.encodeSync(options.error[tag])(
|
||||
failure.value,
|
||||
)
|
||||
options.transport.send(
|
||||
JSON.stringify({
|
||||
_tag: "RpcFailure",
|
||||
id: envelope.id,
|
||||
error,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
|
||||
|
||||
function emit(event: Ev["Type"]): Effect.Effect<void> {
|
||||
return Effect.sync(() => {
|
||||
const encoded = Schema.encodeSync(options.event)(event)
|
||||
options.transport.send(
|
||||
JSON.stringify({ _tag: "RpcEvent", event: encoded }),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return { emit }
|
||||
})
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// JSON-safe data and protocol modules shared by the Godot runtime and the
|
||||
// UI. Runtime-specific behavior stays in packages/game and packages/ui.
|
||||
//
|
||||
// Organized the way `effect` organizes itself: one module per file, each
|
||||
// re-exported here as a namespace.
|
||||
|
||||
export * as Events from "./Events"
|
||||
export * as Procedures from "./Procedures"
|
||||
export * as RpcClient from "./RpcClient"
|
||||
export * as RpcMessage from "./RpcMessage"
|
||||
export * as RpcServer from "./RpcServer"
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["es2022"],
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@codename-painless/effect-godot",
|
||||
"name": "effect-godot",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "nodenext",
|
||||
"lib": ["es2022"],
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
.godot/
|
||||
.godot-bin/
|
||||
node_modules/
|
||||
# Platform CEF runtime binaries (~1GB), fetched by `godot-bin cef fetch`
|
||||
addons/godot_cef/bin/
|
||||
addons/libpainless/bin/
|
||||
gen/
|
||||
typings/*.d.ts
|
||||
*.tsbuildinfo
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Jakob Gillich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,63 +0,0 @@
|
||||
# Foliage3D
|
||||
|
||||
Procedural foliage generation for Terrain3D.
|
||||
|
||||

|
||||
|
||||
**Important**: This addon is in alpha, bugs and breaking changes are to be expected. Always back up your project.
|
||||
|
||||
## Getting started
|
||||
|
||||
After enabling the plugin, create a *Foliage3D* node below a *Terrain3D* node.
|
||||
To set bounds, create a *CollisionShape3D* node below *Foliage3D* with either a box or sphere shape
|
||||
(this will likely be replaced with a custom node in the future).
|
||||
|
||||
Once bounds are set, create a graph. Saving to disk is optional but recommended. The graph editor opens in the bottom panel.
|
||||
Create a *SurfaceSampler* node by right-clicking anywhere and feed its
|
||||
output into a *MeshSpawner* node. To assign a mesh, click on the *MeshSpawner* and add at least one mesh in the sidebar on the right.
|
||||
This should spawn meshes in the 3D viewport. Click the save button under *Foliage3D* to save the meshes into your terrain data, or clear to delete them.
|
||||
|
||||
## Nodes
|
||||
|
||||
#### SurfaceSampler
|
||||
|
||||
Sample points from terrain surface within bounds. Point extents sets a bounding box that, for example, can be used to filter overlapping points with the *Difference* and *Prune* nodes.
|
||||
|
||||
#### FilterPoint
|
||||
|
||||
Filter point position and rotation range. A `0` value equals the respective minimum/maximum value.
|
||||
|
||||
#### Transform
|
||||
|
||||
Randomize transform offset/rotation/scale.
|
||||
|
||||
#### Difference
|
||||
|
||||
Remove points in A that overlap with B.
|
||||
|
||||
#### Prune
|
||||
|
||||
Remove points that overlap with self.
|
||||
|
||||
#### MeshSpawner
|
||||
|
||||
Spawn meshes. Mesh are imported if they do not exist in the current terrain.
|
||||
|
||||
#### TexturePainter
|
||||
|
||||
Pain texture. Textures are imported if they do not exist in the current terrain.
|
||||
This node is slow with many points, use extents modifier and prune to improve performance.
|
||||
|
||||
**Note**: Painted textures are not cleared, previously painted textures are overwritten.
|
||||
|
||||
## Performance
|
||||
|
||||
This plugin uses threads and should perform well up to a certain size, but you do
|
||||
need a good amount of RAM to generate large environments. When you run into problems,
|
||||
consider using multiple Foliage3D nodes to split up generation.
|
||||
|
||||
## Contributing
|
||||
|
||||
UX and performance improvements are welcome. This plugin is inspired by Unreal PCG,
|
||||
however we do not intend to create an exact clone. Features are welcome as well,
|
||||
but justifications should be provided if they deviate meaningfully from PCG.
|
||||
@@ -1,6 +0,0 @@
|
||||
[plugin]
|
||||
name="Foliage3D"
|
||||
description="Procedural foliage generation for Terrain3D"
|
||||
author="Jakob Gillich"
|
||||
version="0.1.0"
|
||||
script="src/plugin.gd"
|
||||
@@ -1,47 +0,0 @@
|
||||
class_name Foliage3DBounds
|
||||
|
||||
var shapes: Array[CollisionShape3D]
|
||||
#var exclusions: Array[CollisionShape3D]
|
||||
|
||||
var aabb: AABB
|
||||
|
||||
func set_shapes(p_shapes: Array[CollisionShape3D]):
|
||||
aabb = AABB()
|
||||
shapes = p_shapes
|
||||
for shape in p_shapes:
|
||||
if shape.shape == null:
|
||||
continue
|
||||
var shape_aabb = get_aabb(shape.global_position, shape.shape)
|
||||
if aabb.position == Vector3.ZERO:
|
||||
aabb = shape_aabb
|
||||
else:
|
||||
aabb = aabb.merge(shape_aabb)
|
||||
|
||||
func contains(position: Vector3) -> bool:
|
||||
for shape in shapes:
|
||||
if shape_contains(position, shape):
|
||||
return true
|
||||
return false
|
||||
|
||||
func shape_contains(position: Vector3, shape: CollisionShape3D) -> bool:
|
||||
if shape.shape is BoxShape3D:
|
||||
var min_corner = shape.global_position - shape.shape.extents
|
||||
var max_corner = shape.global_position + shape.shape.extents
|
||||
return position.x >= min_corner.x and position.x <= max_corner.x and \
|
||||
position.y >= min_corner.y and position.y <= max_corner.y and \
|
||||
position.z >= min_corner.z and position.z <= max_corner.z
|
||||
elif shape.shape is SphereShape3D:
|
||||
return position.distance_to(shape.global_position) <= shape.shape.radius
|
||||
|
||||
return false
|
||||
|
||||
func get_aabb(position: Vector3, shape: Shape3D) -> AABB:
|
||||
if shape is BoxShape3D:
|
||||
return AABB(position - shape.extents, shape.size)
|
||||
elif shape is SphereShape3D:
|
||||
var radius = Vector3(shape.radius, shape.radius, shape.radius)
|
||||
return AABB(position - radius, radius * 2)
|
||||
#elif shape is CapsuleShape3D:
|
||||
#return AABB(position, Vector3(shape.radius * 2, shape.height + shape.radius * 2, shape.radius * 2))
|
||||
|
||||
return AABB(position, Vector3.ZERO)
|
||||
@@ -1 +0,0 @@
|
||||
uid://dk4c8vpi21hxp
|
||||
@@ -1,169 +0,0 @@
|
||||
class_name Foliage3DExecutor
|
||||
|
||||
class Context:
|
||||
var bounds: Foliage3DBounds
|
||||
|
||||
var _data: Dictionary[String, Dictionary]
|
||||
var _data_mut: Mutex = Mutex.new()
|
||||
|
||||
var _terrain: Terrain3D
|
||||
var _terrain_mut: Mutex = Mutex.new()
|
||||
|
||||
var _finalized: Array[String]
|
||||
var _finalized_mut: Mutex = Mutex.new()
|
||||
|
||||
var _main: Array[Callable]
|
||||
var _main_mut: Mutex = Mutex.new()
|
||||
|
||||
func _init(terrain: Terrain3D, bounds: Foliage3DBounds):
|
||||
_terrain = terrain
|
||||
self.bounds = bounds
|
||||
|
||||
func for_node(key: String) -> NodeContext:
|
||||
return NodeContext.new(self, key)
|
||||
|
||||
func duplicate(key: String) -> Dictionary:
|
||||
_data_mut.lock()
|
||||
var data = _data.get(key, {}).duplicate(true)
|
||||
_data_mut.unlock()
|
||||
return data
|
||||
|
||||
func mutate(key: String, fn: Callable) :
|
||||
_data_mut.lock()
|
||||
fn.call(_data.get_or_add(key, {}))
|
||||
_data_mut.unlock()
|
||||
|
||||
func with_terrain(fn: Callable) -> Variant:
|
||||
_terrain_mut.lock()
|
||||
var res = fn.call(_terrain)
|
||||
_terrain_mut.unlock()
|
||||
return res
|
||||
|
||||
func with_terrain_main(fn: Callable) -> void:
|
||||
push_main(with_terrain.bind(fn))
|
||||
|
||||
func finalize(key: String) -> Variant:
|
||||
_finalized_mut.lock()
|
||||
if _finalized.has(key):
|
||||
_finalized_mut.unlock()
|
||||
return false
|
||||
_finalized.append(key)
|
||||
_finalized_mut.unlock()
|
||||
_data_mut.lock()
|
||||
var data = _data.get(key, {})
|
||||
_data_mut.unlock()
|
||||
return data
|
||||
|
||||
func push_main(fn: Callable) -> void:
|
||||
_main.append(fn)
|
||||
|
||||
func pop_main() -> Variant:
|
||||
return _main.pop_back()
|
||||
|
||||
class NodeContext:
|
||||
var _root: Context
|
||||
var _key: String
|
||||
|
||||
var bounds: Foliage3DBounds:
|
||||
get:
|
||||
return _root.bounds
|
||||
|
||||
func _init(context: Context, key: String):
|
||||
_root = context
|
||||
_key = key
|
||||
|
||||
func duplicate() -> Dictionary:
|
||||
return _root.duplicate(_key)
|
||||
|
||||
func mutate(fn: Callable) -> void:
|
||||
_root.mutate(_key, fn)
|
||||
|
||||
func with_terrain(fn: Callable) -> Variant:
|
||||
return _root.with_terrain(fn)
|
||||
|
||||
func with_terrain_main(fn: Callable) -> void:
|
||||
return _root.with_terrain_main(fn)
|
||||
|
||||
func finalize() -> Variant:
|
||||
return _root.finalize(_key)
|
||||
|
||||
func push_main(fn: Callable) -> void:
|
||||
_root.push_main(fn)
|
||||
|
||||
var ctx: Context
|
||||
var all_nodes: Dictionary[String, Foliage3DNode]
|
||||
var remaining_nodes: Dictionary[String, Foliage3DNode]
|
||||
var connections: Array[Dictionary]
|
||||
var threads: Dictionary[String, Thread]
|
||||
|
||||
func _init(p_ctx: Context, p_nodes: Dictionary[String, Foliage3DNode], p_connections: Array[Dictionary]):
|
||||
ctx = p_ctx
|
||||
connections = p_connections.duplicate()
|
||||
all_nodes = p_nodes
|
||||
|
||||
func execute():
|
||||
var total = Time.get_ticks_msec()
|
||||
var durations: Dictionary[String, int]
|
||||
|
||||
for connection in connections:
|
||||
var from: Foliage3DNode = all_nodes[connection["from_node"]]
|
||||
var to: Foliage3DNode = all_nodes[connection["to_node"]]
|
||||
if from == null or to == null:
|
||||
push_error("bad node connection")
|
||||
return
|
||||
to.inputs[connection["to_port"]].append([connection["from_port"], from])
|
||||
|
||||
for fname in ["generate", "post_generate"]:
|
||||
remaining_nodes = all_nodes.duplicate()
|
||||
while remaining_nodes.size() > 0:
|
||||
for name in threads.keys():
|
||||
var thread = threads[name]
|
||||
if not thread.is_alive():
|
||||
connections = connections.filter(func(d: Dictionary): return d["from_node"] != name)
|
||||
remaining_nodes.erase(name)
|
||||
thread.wait_to_finish()
|
||||
threads.erase(name)
|
||||
durations[name] = Time.get_ticks_msec() - durations[name]
|
||||
|
||||
# TODO max threads config
|
||||
for i in range(4 - threads.size()):
|
||||
var name := get_next()
|
||||
if name.is_empty():
|
||||
break
|
||||
var thread = Thread.new()
|
||||
var node = remaining_nodes[name]
|
||||
var fn = node[fname].bind(ctx.for_node(node.type()))
|
||||
thread.start(fn)
|
||||
threads[name] = thread
|
||||
durations[name] = Time.get_ticks_msec()
|
||||
|
||||
var fn = ctx.pop_main()
|
||||
while fn != null:
|
||||
fn.call()
|
||||
fn = ctx.pop_main()
|
||||
|
||||
OS.delay_msec(10)
|
||||
|
||||
threads.clear()
|
||||
print_stats(Time.get_ticks_msec() - total, durations)
|
||||
|
||||
func print_stats(total: int, durations: Dictionary[String, int]):
|
||||
var by_node: Dictionary[String, int]
|
||||
for name in durations.keys():
|
||||
var type = all_nodes[name].type()
|
||||
by_node[type] = by_node.get(type, 0) + durations[name]
|
||||
|
||||
var keys = by_node.keys()
|
||||
keys.sort_custom(func(a, b): return by_node[a] > by_node[b])
|
||||
var bottom3 = keys.slice(0, 3).map(func(k): return "%s in %dms" % [k, by_node[k]])
|
||||
var text = "Foliage3D generation completed in %dms (bottom 3: %s, %s, %s)" % ([total] + bottom3)
|
||||
print_debug(text)
|
||||
|
||||
func get_next() -> String:
|
||||
for name in remaining_nodes.keys():
|
||||
if threads.keys().has(name):
|
||||
continue
|
||||
if connections.filter(func(d: Dictionary): return d["to_node"] == name).is_empty():
|
||||
return name
|
||||
|
||||
return ""
|
||||
@@ -1 +0,0 @@
|
||||
uid://trswuoqp2ob6
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-leaf-icon lucide-leaf"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="foliage3d.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="17.088414"
|
||||
inkscape:cx="-1.8726138"
|
||||
inkscape:cy="19.135772"
|
||||
inkscape:window-width="1720"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="20"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"
|
||||
id="path1"
|
||||
style="stroke:#fc7f7f;stroke-opacity:1" />
|
||||
<path
|
||||
d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"
|
||||
id="path2"
|
||||
style="stroke:#fc7f7f;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,43 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://wf16dssf81mf"
|
||||
path="res://.godot/imported/foliage3d.svg-6ac372c3b956f2b6bc796ca56aa78c19.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/foliage_3d/src/foliage3d.svg"
|
||||
dest_files=["res://.godot/imported/foliage3d.svg-6ac372c3b956f2b6bc796ca56aa78c19.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -1,142 +0,0 @@
|
||||
@tool @icon("foliage3d.svg")
|
||||
class_name Foliage3D extends Node3D
|
||||
|
||||
const Foliage3DGraphEdit := preload("graph_edit.gd")
|
||||
|
||||
static var NODES = {
|
||||
"Difference": Foliage3DDifference,
|
||||
"Prune": Foliage3DPrune,
|
||||
"SurfaceSampler": Foliage3DSurfaceSampler,
|
||||
"MeshSpawner": Foliage3DMeshSpawner,
|
||||
"Transform": Foliage3DTransform,
|
||||
"Filter": Foliage3DFilter,
|
||||
"FilterPoint": Foliage3DFilterPoint,
|
||||
"Slice": Foliage3DSlice,
|
||||
"ExtentsModifier": Foliage3DExtentsModifier,
|
||||
"TexturePainter": Foliage3DTexturePainter,
|
||||
}
|
||||
|
||||
signal graph_changed()
|
||||
|
||||
@export_tool_button("Generate", "Callable") var _gen = generate
|
||||
@export_tool_button("Clear", "Callable") var _clear = clear
|
||||
@export_tool_button("Save", "Callable") var _save = save
|
||||
|
||||
|
||||
@export var auto_generate: bool = true
|
||||
@export var auto_save: bool = false
|
||||
|
||||
@export var graph: Foliage3DGraph:
|
||||
set(value):
|
||||
graph = value
|
||||
graph_changed.emit()
|
||||
|
||||
# region>mesh>transform
|
||||
@export var generated: Dictionary[Vector2i, Dictionary] = {}
|
||||
|
||||
var terrain: Terrain3D
|
||||
var timer: Timer
|
||||
var num_timer: int
|
||||
|
||||
var bounds := Foliage3DBounds.new()
|
||||
var edit: Foliage3DGraphEdit
|
||||
|
||||
func _ready() -> void:
|
||||
if not get_parent() is Terrain3D:
|
||||
return
|
||||
terrain = get_parent()
|
||||
|
||||
timer = Timer.new()
|
||||
timer.wait_time = 1.0
|
||||
timer.one_shot = true
|
||||
timer.timeout.connect(generate)
|
||||
add_child(timer)
|
||||
|
||||
set_notify_transform(true)
|
||||
|
||||
update_bounds()
|
||||
|
||||
#func _exit_tree() -> void:
|
||||
#if graph != null:
|
||||
#graph.changed.disconnect(queue_gen)
|
||||
|
||||
func create_edit() -> Foliage3DGraphEdit:
|
||||
edit = Foliage3DGraphEdit.new(graph, terrain, bounds)
|
||||
edit.changed.connect(queue_generate)
|
||||
edit.mesh_xforms_added.connect(func(region: Vector2i, mesh: int, xforms: Array[Transform3D]):
|
||||
generated.get_or_add(region, {}).get_or_add(mesh, []).append_array(xforms)
|
||||
)
|
||||
return edit
|
||||
|
||||
func update_bounds():
|
||||
var shapes: Array[CollisionShape3D]
|
||||
for child in get_children():
|
||||
if child is CollisionShape3D:
|
||||
shapes.append(child)
|
||||
bounds.set_shapes(shapes)
|
||||
|
||||
func _notification(what):
|
||||
match what:
|
||||
NOTIFICATION_TRANSFORM_CHANGED:
|
||||
queue_generate()
|
||||
|
||||
func _get_configuration_warnings():
|
||||
if terrain == null:
|
||||
return ["Foliage3D must be child of Terrain3D"]
|
||||
return []
|
||||
|
||||
func queue_generate():
|
||||
if not auto_generate:
|
||||
return
|
||||
if timer != null:
|
||||
timer.start()
|
||||
|
||||
func generate():
|
||||
update_bounds()
|
||||
clear()
|
||||
edit.generate()
|
||||
if auto_save:
|
||||
save()
|
||||
|
||||
func clear():
|
||||
# https://terrain3d.readthedocs.io/en/latest/api/class_terrain3dregion.html#class-terrain3dregion-property-instances
|
||||
for region_loc in generated.keys():
|
||||
var region = terrain.data.get_region(region_loc)
|
||||
if region == null:
|
||||
continue
|
||||
|
||||
region.modified = true
|
||||
var region_generated = generated[region_loc]
|
||||
var instances = region.instances
|
||||
|
||||
for mesh in instances.keys():
|
||||
if not region_generated.has(mesh):
|
||||
continue
|
||||
|
||||
var mesh_generated = region_generated[mesh]
|
||||
var mesh_cells = instances[mesh]
|
||||
|
||||
for cell in mesh_cells.keys():
|
||||
var arr = mesh_cells[cell]
|
||||
if arr.is_empty():
|
||||
continue
|
||||
|
||||
var xforms = arr[0]
|
||||
var filtered = xforms.filter(func(xform: Transform3D) -> bool:
|
||||
return not mesh_generated.has(xform)
|
||||
)
|
||||
|
||||
if filtered.is_empty():
|
||||
mesh_cells.erase(cell)
|
||||
else:
|
||||
arr[0] = filtered
|
||||
|
||||
generated = {}
|
||||
terrain.instancer.update_mmis(true)
|
||||
|
||||
func save():
|
||||
terrain.data.save_directory(terrain.data_directory)
|
||||
|
||||
func _validate_property(property: Dictionary):
|
||||
if property.name in ["generated"]:
|
||||
property.usage = PROPERTY_USAGE_NO_EDITOR
|
||||
@@ -1 +0,0 @@
|
||||
uid://smqrc7uef45a
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
@tool
|
||||
class_name Foliage3DGraph extends Resource
|
||||
|
||||
@export var nodes: Array[Dictionary]
|
||||
@export var connections: Array[Dictionary]
|
||||
@@ -1 +0,0 @@
|
||||
uid://tkade2qgt1mn
|
||||
@@ -1,121 +0,0 @@
|
||||
@tool
|
||||
extends GraphEdit
|
||||
|
||||
const Foliage3DGraphEditNode = preload("graph_edit_node.gd")
|
||||
|
||||
signal changed()
|
||||
signal mesh_xforms_added(region: Vector2i, mesh: int, xforms: Array[Vector3])
|
||||
|
||||
@onready var node_popup: Window = preload("node_popup.tscn").instantiate()
|
||||
|
||||
var resource: Foliage3DGraph
|
||||
var terrain: Terrain3D
|
||||
var bounds: Foliage3DBounds
|
||||
|
||||
func _init(p_resource: Foliage3DGraph, p_terrain: Terrain3D, p_bounds: Foliage3DBounds):
|
||||
resource = p_resource
|
||||
terrain = p_terrain
|
||||
bounds = p_bounds
|
||||
size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
func _ready():
|
||||
for params in resource.nodes:
|
||||
var node = create_node(params["resource_name"], params)
|
||||
add_child(node)
|
||||
|
||||
for connection in resource.connections:
|
||||
connection["from_node"] = connection["from_node"].validate_node_name()
|
||||
connection["to_node"] = connection["to_node"].validate_node_name()
|
||||
var err = connect_node(connection["from_node"], connection["from_port"], connection["to_node"], connection["to_port"])
|
||||
if err != OK:
|
||||
push_error("foliage: connect_node error %d" % err)
|
||||
|
||||
add_child(node_popup)
|
||||
node_popup.node_created.connect(_on_node_created)
|
||||
popup_request.connect(_show_add_node_popup)
|
||||
connection_request.connect(_on_connection_request)
|
||||
delete_nodes_request.connect(_on_delete_nodes_request)
|
||||
disconnection_request.connect(_on_disconnection_request)
|
||||
end_node_move.connect(_on_end_node_move)
|
||||
node_selected.connect(_on_node_selected)
|
||||
|
||||
add_valid_connection_type(TYPE_TRANSFORM3D, TYPE_TRANSFORM3D)
|
||||
|
||||
right_disconnects = true
|
||||
|
||||
func create_node(type: String, params: Dictionary = {}) -> Foliage3DGraphEditNode:
|
||||
var node = Foliage3DGraphEditNode.new(type, params)
|
||||
node.changed.connect(func():
|
||||
save()
|
||||
changed.emit()
|
||||
)
|
||||
node.mesh_xforms_added.connect(mesh_xforms_added.emit)
|
||||
return node
|
||||
|
||||
func generate():
|
||||
var nodes: Dictionary[String, Foliage3DNode]
|
||||
for child in get_children():
|
||||
if child is Foliage3DGraphEditNode:
|
||||
nodes[str(child.name)] = child.node
|
||||
|
||||
var ctx = Foliage3DExecutor.Context.new(terrain, bounds)
|
||||
var executor = Foliage3DExecutor.new(ctx, nodes, connections)
|
||||
|
||||
# TODO display "generating..." window with option to cancel
|
||||
executor.execute()
|
||||
|
||||
func save():
|
||||
resource.nodes = []
|
||||
resource.connections = connections
|
||||
|
||||
for node in get_children():
|
||||
if node is Foliage3DGraphEditNode:
|
||||
var dict = {
|
||||
"name": node.get_name().validate_node_name(),
|
||||
"position_offset": node.position_offset
|
||||
}
|
||||
for prop in node.node.get_property_list():
|
||||
if (prop.usage & PROPERTY_USAGE_STORAGE) == 0:
|
||||
continue
|
||||
if ["terrain", "slots", "script"].has(prop["name"]):
|
||||
continue
|
||||
dict[prop["name"]] = node.node.get(prop["name"])
|
||||
resource.nodes.append(dict)
|
||||
|
||||
if not resource.resource_path.is_empty():
|
||||
ResourceSaver.save(resource, resource.resource_path)
|
||||
|
||||
func _on_node_selected(node):
|
||||
EditorInterface.edit_resource(node.node)
|
||||
|
||||
func _on_node_created(type: String, node_offset: Vector2):
|
||||
var node = create_node(type)
|
||||
node.position_offset = node_offset
|
||||
add_child(node)
|
||||
save()
|
||||
|
||||
func _on_connection_request(from: StringName, fport: int, to: StringName, tport: int):
|
||||
if from == to:
|
||||
return
|
||||
connect_node(from, fport, to, tport)
|
||||
save()
|
||||
changed.emit()
|
||||
|
||||
func _on_delete_nodes_request(nodes: Array[StringName]) -> void:
|
||||
for node in nodes:
|
||||
get_node(NodePath(node)).free()
|
||||
save()
|
||||
changed.emit()
|
||||
|
||||
func _on_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
||||
disconnect_node(from_node, from_port, to_node, to_port)
|
||||
save()
|
||||
changed.emit()
|
||||
|
||||
func _on_end_node_move():
|
||||
save()
|
||||
|
||||
func _show_add_node_popup(click_position: Vector2i) -> void:
|
||||
node_popup.node_position = (get_local_mouse_position() + scroll_offset) / zoom
|
||||
node_popup.popup(Rect2i(Vector2i(DisplayServer.mouse_get_position()), node_popup.size))
|
||||
@@ -1 +0,0 @@
|
||||
uid://rxhkt40jgqcs
|
||||
@@ -1,48 +0,0 @@
|
||||
@tool
|
||||
extends GraphNode
|
||||
|
||||
signal changed()
|
||||
signal mesh_xforms_added(region: Vector2i, mesh: int, xforms: Array[Vector3])
|
||||
|
||||
var node: Foliage3DNode
|
||||
|
||||
func _init(type: String, props: Dictionary = {}):
|
||||
node = Foliage3D.NODES[type].new()
|
||||
node.changed.connect(changed.emit)
|
||||
node.mesh_xforms_added.connect(mesh_xforms_added.emit)
|
||||
title = type
|
||||
|
||||
if not props.is_empty():
|
||||
name = props["name"]
|
||||
position_offset = props["position_offset"]
|
||||
|
||||
for i in range(max(node.get_inputs().size(), node.get_outputs().size())):
|
||||
var hbox := HBoxContainer.new()
|
||||
var has_input: bool
|
||||
var input_type = TYPE_VECTOR3
|
||||
if node.get_inputs().size() > i:
|
||||
has_input = true
|
||||
input_type = node.get_inputs()[i]
|
||||
var label = Label.new()
|
||||
label.text = "in"
|
||||
hbox.add_child(label)
|
||||
var has_output: bool
|
||||
var output_type = TYPE_VECTOR3
|
||||
if node.get_outputs().size() > i:
|
||||
has_output = true
|
||||
output_type = node.get_outputs()[i]
|
||||
var label = Label.new()
|
||||
label.text = "out"
|
||||
label.size_flags_horizontal = Control.SIZE_SHRINK_END | Control.SIZE_EXPAND
|
||||
hbox.add_child(label)
|
||||
set_slot(i, has_input, input_type, Color.BLUE, has_output, output_type, Color.BLUE)
|
||||
add_child(hbox)
|
||||
|
||||
for prop in node.get_property_list():
|
||||
if (prop.usage & PROPERTY_USAGE_STORAGE) == 0:
|
||||
continue
|
||||
# TODO not sure why all C++ props are included
|
||||
if ["terrain", "slots", "script", "resource_name"].has(prop["name"]):
|
||||
continue
|
||||
if props.has(prop["name"]):
|
||||
node.set(prop["name"], props[prop["name"]])
|
||||
@@ -1 +0,0 @@
|
||||
uid://i5tdnb5mu44q
|
||||
@@ -1,7 +0,0 @@
|
||||
class_name Foliage3DEditorInspectorPlugin extends EditorInspectorPlugin
|
||||
|
||||
func _can_handle(object):
|
||||
return true
|
||||
|
||||
func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide):
|
||||
return false
|
||||
@@ -1 +0,0 @@
|
||||
uid://cw48v08km172g
|
||||
@@ -1,7 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DMeshAsset extends Resource
|
||||
|
||||
@export var scene: PackedScene
|
||||
|
||||
# TODO
|
||||
# var weight: float
|
||||
@@ -1 +0,0 @@
|
||||
uid://gvg7dvp2ie3g
|
||||
@@ -1,47 +0,0 @@
|
||||
@tool
|
||||
extends Window
|
||||
|
||||
signal node_created(String, Vector2i)
|
||||
|
||||
@onready var tree: Tree = %Tree
|
||||
var node_position: Vector2
|
||||
|
||||
func _ready() -> void:
|
||||
refresh()
|
||||
about_to_popup.connect(refresh)
|
||||
close_requested.connect(hide)
|
||||
focus_exited.connect(hide)
|
||||
|
||||
tree.item_activated.connect(_on_item_activated)
|
||||
tree.item_selected.connect(_on_item_selected)
|
||||
tree.nothing_selected.connect(_on_nothing_selected)
|
||||
|
||||
%CreateButton.pressed.connect(_on_item_activated)
|
||||
%CancelButton.pressed.connect(hide)
|
||||
|
||||
func refresh():
|
||||
tree.clear()
|
||||
var root = tree.create_item()
|
||||
|
||||
for type in Foliage3D.NODES.keys():
|
||||
var item = tree.create_item(root)
|
||||
item.set_text(0, type)
|
||||
#item.set_tooltip_text(0, "description")
|
||||
item.set_metadata(0, type)
|
||||
|
||||
func _on_item_selected() -> void:
|
||||
var item = tree.get_selected()
|
||||
if item == null:
|
||||
_on_nothing_selected()
|
||||
return
|
||||
%CreateButton.disabled = false
|
||||
|
||||
func _on_nothing_selected() -> void:
|
||||
%CreateButton.disabled = true
|
||||
|
||||
func _on_item_activated() -> void:
|
||||
var selected = tree.get_selected()
|
||||
if selected == null:
|
||||
return
|
||||
node_created.emit(selected.get_metadata(0), node_position)
|
||||
hide()
|
||||
@@ -1 +0,0 @@
|
||||
uid://besoxm36sku3c
|
||||
@@ -1,67 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bd3iulla58u3y"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://besoxm36sku3c" path="res://addons/foliage_3d/src/node_popup.gd" id="1_kwkgp"]
|
||||
|
||||
[node name="NodePopup" type="Window"]
|
||||
title = "Create node"
|
||||
size = Vector2i(300, 400)
|
||||
visible = false
|
||||
transient = true
|
||||
popup_window = true
|
||||
script = ExtResource("1_kwkgp")
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 14
|
||||
theme_override_constants/margin_top = 14
|
||||
theme_override_constants/margin_right = 14
|
||||
theme_override_constants/margin_bottom = 14
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Search" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Search"
|
||||
clear_button_enabled = true
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="Tree" type="Tree" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
hide_root = true
|
||||
|
||||
[node name="ActionContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CancelButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/ActionContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Cancel"
|
||||
|
||||
[node name="CreateButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/ActionContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
disabled = true
|
||||
text = "Create"
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "difference.hpp"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
Foliage3DDifference::Foliage3DDifference()
|
||||
{
|
||||
// Foliage3DSlot* inout = memnew(Foliage3DSlot);
|
||||
// inout->left->type = Foliage3DPort::Type::POINT;
|
||||
// inout->right->type = Foliage3DPort::Type::POINT;
|
||||
|
||||
// _slots.append(inout);
|
||||
}
|
||||
|
||||
Foliage3DDifference::~Foliage3DDifference()
|
||||
{
|
||||
}
|
||||
|
||||
Array Foliage3DDifference::_execute(TypedArray<Foliage3DPoint> p_input, TypedArray<Foliage3DPoint> p_diff)
|
||||
{
|
||||
TypedArray<Foliage3DPoint> points = p_input.duplicate();
|
||||
for (int i = p_input.size() - 1; i >= 0; i--) {
|
||||
Foliage3DPoint* p1 = cast_to<Foliage3DPoint>(p_input[i]);
|
||||
for (int j = 0; j < p_diff.size(); j++) {
|
||||
Foliage3DPoint* p2 = cast_to<Foliage3DPoint>(p_diff[j]);
|
||||
if (p1->intersects(p2))
|
||||
{
|
||||
points.remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array result;
|
||||
result.append(points);
|
||||
return result;
|
||||
}
|
||||
|
||||
TypedArray<int> Foliage3DDifference::get_inputs()
|
||||
{
|
||||
TypedArray<int> inputs;
|
||||
inputs.append(Variant::Type::TRANSFORM3D);
|
||||
inputs.append(Variant::Type::TRANSFORM3D);
|
||||
return inputs;
|
||||
}
|
||||
|
||||
TypedArray<int> Foliage3DDifference::get_outputs()
|
||||
{
|
||||
TypedArray<int> outputs;
|
||||
outputs.append(Variant::Type::TRANSFORM3D);
|
||||
return outputs;
|
||||
}
|
||||
|
||||
void Foliage3DDifference::_bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("_execute"), &Foliage3DDifference::_execute);
|
||||
ClassDB::bind_method(D_METHOD("get_inputs"), &Foliage3DDifference::get_inputs);
|
||||
ClassDB::bind_method(D_METHOD("get_outputs"), &Foliage3DDifference::get_outputs);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DDifference extends Foliage3DNode
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT, TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
# removes all points within the extents of diff
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint] = [], diff: Array[Foliage3DPoint] = []) -> Array:
|
||||
var result = Foliage3DPoint.Grid.new(diff).difference(input)
|
||||
return [result]
|
||||
@@ -1 +0,0 @@
|
||||
uid://dljr6ntao15cj
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "point.hpp"
|
||||
#include "node.hpp"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
class Foliage3DDifference : public Foliage3DNode
|
||||
{
|
||||
GDCLASS(Foliage3DDifference, Foliage3DNode);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
Array _execute(TypedArray<Foliage3DPoint> p_input, TypedArray<Foliage3DPoint> p_diff);
|
||||
|
||||
public:
|
||||
Foliage3DDifference();
|
||||
~Foliage3DDifference();
|
||||
|
||||
TypedArray<int> get_inputs();
|
||||
TypedArray<int> get_outputs();
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
class_name Foliage3DExtentsModifier extends Foliage3DNode
|
||||
|
||||
enum Mode {
|
||||
SET,
|
||||
MULTIPLY,
|
||||
ADD
|
||||
}
|
||||
|
||||
@export var point_extents: Vector3 = Vector3(1, 1, 1):
|
||||
set(value):
|
||||
point_extents = value
|
||||
changed.emit()
|
||||
|
||||
@export var mode: Mode
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
var points: Array[Foliage3DPoint]
|
||||
points.assign(input.map(func(p): return p.duplicate()))
|
||||
for point in points:
|
||||
match mode:
|
||||
Mode.SET:
|
||||
point.extents = point_extents
|
||||
Mode.MULTIPLY:
|
||||
point.extents *= point_extents
|
||||
Mode.ADD:
|
||||
point.extents += point_extents
|
||||
return [points]
|
||||
@@ -1 +0,0 @@
|
||||
uid://cp1twl3xj43pw
|
||||
@@ -1,37 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DFilter extends Foliage3DNode
|
||||
|
||||
@export var expression: String = "true":
|
||||
set(value):
|
||||
expression = value
|
||||
changed.emit()
|
||||
|
||||
# TODO accept any object type
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT, TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
var out: Array[Foliage3DPoint]
|
||||
var rest: Array[Foliage3DPoint]
|
||||
|
||||
var exp = Expression.new()
|
||||
var err = exp.parse(expression, ["p"])
|
||||
if err != OK:
|
||||
push_warning("foliage: bad expression '%s'" % expression)
|
||||
return [out, rest]
|
||||
|
||||
for point in input:
|
||||
var res = exp.execute([point])
|
||||
if res is bool:
|
||||
if res:
|
||||
out.append(point)
|
||||
else:
|
||||
rest.append(point)
|
||||
else:
|
||||
push_warning("foliage: bad expression '%s'" % expression)
|
||||
break
|
||||
|
||||
return [out, rest]
|
||||
@@ -1 +0,0 @@
|
||||
uid://b0giscjsnwugo
|
||||
@@ -1,64 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DFilterPoint extends Foliage3DNode
|
||||
|
||||
@export var position_min: Vector3:
|
||||
set(value):
|
||||
position_min = value
|
||||
changed.emit()
|
||||
|
||||
@export var position_max: Vector3:
|
||||
set(value):
|
||||
position_max = value
|
||||
changed.emit()
|
||||
|
||||
@export var rotation_min: Vector3:
|
||||
set(value):
|
||||
rotation_min = value
|
||||
changed.emit()
|
||||
|
||||
@export var rotation_max: Vector3:
|
||||
set(value):
|
||||
rotation_max = value
|
||||
changed.emit()
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT, TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
var result: Array[Foliage3DPoint]
|
||||
var rest: Array[Foliage3DPoint]
|
||||
|
||||
for point in input:
|
||||
if position_min != Vector3.ZERO or position_max != Vector3.ZERO:
|
||||
if point.transform.origin.clamp(vec_defaults(position_min, -INF), vec_defaults(position_max)) != point.transform.origin:
|
||||
rest.append(point)
|
||||
continue
|
||||
if rotation_min != Vector3.ZERO or rotation_max != Vector3.ZERO:
|
||||
var rad_min = vec_defaults(rotation_min, 0.0) * deg_to_rad(1)
|
||||
var rad_max = vec_defaults(rotation_max, 360.0) * deg_to_rad(1)
|
||||
var rad = point.transform.basis.get_euler()
|
||||
if not filter_rotation_vector(rad, rad_min, rad_max):
|
||||
rest.append(point)
|
||||
continue
|
||||
|
||||
result.append(point)
|
||||
|
||||
return [result, rest]
|
||||
|
||||
func filter_rotation_vector(value: Vector3, min: Vector3, max: Vector3):
|
||||
for axis in ["x", "y", "z"]:
|
||||
if value[axis] < 0:
|
||||
value[axis] *= -1
|
||||
if value[axis] > max[axis] or value[axis] < min[axis]:
|
||||
return false
|
||||
return true
|
||||
|
||||
func vec_defaults(vec: Vector3, default = INF) -> Vector3:
|
||||
return Vector3(
|
||||
vec.x if vec.x != 0.0 else default,
|
||||
vec.y if vec.y != 0.0 else default,
|
||||
vec.z if vec.z != 0.0 else default
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
uid://ca55qfd76hqul
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "graph.hpp"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
// using namespace godot;
|
||||
|
||||
// Foliage3DGraph::Foliage3DGraph()
|
||||
// {
|
||||
|
||||
// }
|
||||
|
||||
// Foliage3DGraph::~Foliage3DGraph()
|
||||
// {
|
||||
// }
|
||||
|
||||
|
||||
// void Foliage3DGraph::load(Array nodes, Array connections)
|
||||
// {
|
||||
// _nodes = TypedArray<Foliage3DNode>();
|
||||
|
||||
// for (int i = 0; i < nodes.size(); i++) {
|
||||
// Dictionary dict = nodes[i];
|
||||
// String str = dict["node"];
|
||||
// std::string node(str.utf8().get_data());
|
||||
// switch(node) {
|
||||
// case "foo":
|
||||
// // code block
|
||||
// break;
|
||||
// case y:
|
||||
// // code block
|
||||
// break;
|
||||
// default:
|
||||
// // code block
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// void Foliage3DGraph::_bind_methods()
|
||||
// {
|
||||
// // ClassDB::bind_method(D_METHOD("gen", "input", "diff"), &Foliage3DGraph::gen);
|
||||
// }
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "point.hpp"
|
||||
#include "node.hpp"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
// class Foliage3DGraph : public Object
|
||||
// {
|
||||
// GDCLASS(Foliage3DGraph, Object);
|
||||
|
||||
// protected:
|
||||
// static void _bind_methods();
|
||||
|
||||
// TypedArray<Foliage3DNode> _nodes;
|
||||
// public:
|
||||
// Foliage3DGraph();
|
||||
// ~Foliage3DGraph();
|
||||
|
||||
// void load(Array nodes, Array connections);
|
||||
// Dictionary gen(Terrain3D terrain);
|
||||
// };
|
||||
@@ -1,86 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DMeshSpawner extends Foliage3DNode
|
||||
|
||||
@export var assets: Array[Foliage3DMeshAsset]:
|
||||
set(value):
|
||||
assets = value
|
||||
changed.emit()
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return []
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, points: Array[Foliage3DPoint]) -> Array:
|
||||
var ids: Array[int]
|
||||
for i in range(assets.size()):
|
||||
if assets[i].scene == null:
|
||||
continue
|
||||
var id = ctx.with_terrain(get_or_create_mesh.bind(assets[i].scene))
|
||||
ids.append(id)
|
||||
|
||||
if ids.is_empty():
|
||||
return []
|
||||
|
||||
var xforms: Array[Array]
|
||||
for i in range(ids.size()):
|
||||
xforms.append([] as Array[Transform3D])
|
||||
|
||||
for i in range(0, points.size(), ids.size()):
|
||||
for j in range(min(ids.size(), points.size()-i)):
|
||||
xforms[j].append(points[i+j].transform)
|
||||
|
||||
for i in range(ids.size()):
|
||||
ctx.with_terrain(add_mesh_xforms.bind(ctx, ids[i], xforms[i]))
|
||||
|
||||
return []
|
||||
|
||||
func get_or_create_mesh(terrain: Terrain3D, mesh: PackedScene) -> int:
|
||||
var mesh_count = terrain.assets.get_mesh_count()
|
||||
for i in range(mesh_count):
|
||||
var asset = terrain.assets.get_mesh_asset(i)
|
||||
if asset == null or asset.scene_file == null:
|
||||
continue
|
||||
if mesh.resource_path == asset.scene_file.resource_path:
|
||||
return i
|
||||
|
||||
var asset := Terrain3DMeshAsset.new()
|
||||
asset.scene_file = mesh
|
||||
terrain.assets.set_mesh_asset(mesh_count, asset)
|
||||
return mesh_count
|
||||
|
||||
|
||||
func add_mesh_xforms(terrain: Terrain3D, ctx: Foliage3DExecutor.NodeContext, mesh: int, xforms: Array[Transform3D]) -> void:
|
||||
if mesh >= terrain.assets.get_mesh_count():
|
||||
push_warning("foliage: mesh %d out of range" % mesh)
|
||||
return
|
||||
var dict: Dictionary = {}
|
||||
var asset := terrain.assets.get_mesh_asset(mesh)
|
||||
for i in range(xforms.size()):
|
||||
var xform = xforms[i]
|
||||
xform.origin += xform.basis.y * asset.height_offset
|
||||
var loc = terrain.data.get_region_location(xform.origin)
|
||||
|
||||
if not terrain.data.has_region(loc):
|
||||
terrain.data.add_region_blank(loc)
|
||||
|
||||
var region: Array[Transform3D] = dict.get_or_add(loc, [] as Array[Transform3D])
|
||||
region.append(xform)
|
||||
|
||||
for loc in dict.keys():
|
||||
var colors: PackedColorArray = []
|
||||
colors.resize(dict[loc].size())
|
||||
colors.fill(Color.WHITE)
|
||||
|
||||
var region = terrain.data.get_region(loc)
|
||||
region.modified = true
|
||||
|
||||
var global_local_offset = Vector3(loc.x, 0, loc.y) * region.region_size * terrain.vertex_spacing
|
||||
for i in range(dict[loc].size()):
|
||||
dict[loc][i].origin -= global_local_offset
|
||||
|
||||
ctx.with_terrain_main(func(terrain: Terrain3D):
|
||||
terrain.instancer.append_region(region, mesh, dict[loc], colors)
|
||||
)
|
||||
mesh_xforms_added.emit(loc, mesh, dict[loc])
|
||||
@@ -1 +0,0 @@
|
||||
uid://b3ldpkr2ml5lo
|
||||
@@ -1,126 +0,0 @@
|
||||
#include "node.hpp"
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
|
||||
Foliage3DNode::Foliage3DNode()
|
||||
{
|
||||
_terrain = memnew(Foliage3DTerrain);
|
||||
|
||||
// promise<Variant> promise;
|
||||
// _promise = &promise;
|
||||
// future<Variant> future;
|
||||
// _result = &future.share();
|
||||
|
||||
// int num_inputs;
|
||||
// for (int i = 0; i < _slots.size(); i++)
|
||||
// {
|
||||
// Foliage3DSlot* slot = cast_to<Foliage3DSlot>(_inputs[i]);
|
||||
// if (slot->left) {
|
||||
// num_inputs++;
|
||||
// }
|
||||
// }
|
||||
// _inputs.resize(num_inputs);
|
||||
}
|
||||
|
||||
Foliage3DNode::~Foliage3DNode()
|
||||
{
|
||||
}
|
||||
|
||||
void Foliage3DNode::execute()
|
||||
{
|
||||
if (_inputs.size() != get_inputs().size())
|
||||
{
|
||||
// TODO optional inputs
|
||||
ERR_PRINT("input count mismatch");
|
||||
return;
|
||||
}
|
||||
|
||||
Array inputs;
|
||||
for (int i = _inputs.size() - 1; i >= 0; i--)
|
||||
{
|
||||
Foliage3DNode* node = cast_to<Foliage3DNode>(_inputs[i]);
|
||||
Variant result = node->get_result();
|
||||
if (result.get_type() != Variant::Type::ARRAY)
|
||||
{
|
||||
ERR_PRINT("bad result type");
|
||||
return;
|
||||
}
|
||||
Array arr = result;
|
||||
// TODO support multiple out ports
|
||||
inputs.append(arr[0]);
|
||||
}
|
||||
|
||||
//_result = callv("_execute", inputs);
|
||||
// _promise->set_value()
|
||||
// _promise->set_value(result);
|
||||
}
|
||||
|
||||
|
||||
void Foliage3DNode::set_params(Dictionary p_dict)
|
||||
{
|
||||
Array keys = p_dict.keys();
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
String key = keys[i];
|
||||
if (key == "node")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
set(keys[i], p_dict[keys[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
void Foliage3DNode::set_input(Variant p_node, int slot)
|
||||
{
|
||||
// ASSERT(slot > _inputs.size(), void());
|
||||
Foliage3DNode* node = cast_to<Foliage3DNode>(p_node);
|
||||
|
||||
_inputs[slot] = node;
|
||||
}
|
||||
|
||||
// void Foliage3DNode::set_slots(TypedArray<Foliage3DSlot> p_slots)
|
||||
// {
|
||||
// _slots = p_slots;
|
||||
// }
|
||||
|
||||
// TypedArray<Foliage3DSlot> Foliage3DNode::get_slots()
|
||||
// {
|
||||
// return _slots;
|
||||
// }
|
||||
|
||||
TypedArray<int> Foliage3DNode::get_inputs()
|
||||
{
|
||||
TypedArray<int> inputs;
|
||||
return inputs;
|
||||
}
|
||||
|
||||
TypedArray<int> Foliage3DNode::get_outputs()
|
||||
{
|
||||
TypedArray<int> outputs;
|
||||
return outputs;
|
||||
}
|
||||
|
||||
|
||||
void Foliage3DNode::_bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("get_terrain"), &Foliage3DNode::get_terrain);
|
||||
ClassDB::bind_method(D_METHOD("set_terrain", "terrain"), &Foliage3DNode::set_terrain);
|
||||
|
||||
// ClassDB::bind_method(D_METHOD("get_slots"), &Foliage3DNode::get_slots);
|
||||
// ClassDB::bind_method(D_METHOD("set_slots", "slots"), &Foliage3DNode::set_slots);
|
||||
|
||||
// ClassDB::bind_method(D_METHOD("get_params"), &Foliage3DNode::get_params);
|
||||
ClassDB::bind_method(D_METHOD("set_params", "params"), &Foliage3DNode::set_params);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_input", "node", "slot"), &Foliage3DNode::set_input);
|
||||
ClassDB::bind_method(D_METHOD("execute"), &Foliage3DNode::execute);
|
||||
|
||||
GDVIRTUAL_BIND(get_inputs);
|
||||
GDVIRTUAL_BIND(get_outputs);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "terrain"), "set_terrain", "get_terrain");
|
||||
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "slots"), "set_slots", "get_slots");
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DNode extends Resource
|
||||
|
||||
enum {
|
||||
TYPE_POINT = 100
|
||||
}
|
||||
|
||||
signal mesh_xforms_added(region: Vector2i, mesh: int, xforms: Array[Vector3])
|
||||
|
||||
var inputs: Array[Array]
|
||||
var result: Array
|
||||
|
||||
func _init():
|
||||
inputs.resize(get_inputs().size())
|
||||
resource_name = type()
|
||||
for i in range(inputs.size()):
|
||||
inputs[i] = []
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return []
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return []
|
||||
|
||||
func generate(ctx: Foliage3DExecutor.NodeContext):
|
||||
if OS.get_thread_caller_id() != OS.get_main_thread_id():
|
||||
Thread.set_thread_safety_checks_enabled(false)
|
||||
|
||||
var args = []
|
||||
|
||||
for i in range(inputs.size()):
|
||||
for j in inputs[i]:
|
||||
var port = j[0]
|
||||
var node = j[1]
|
||||
if args.size() <= i:
|
||||
args.append(node.result[port].duplicate())
|
||||
else:
|
||||
args[i].append_array(node.result[port])
|
||||
|
||||
if args.size() != get_inputs().size():
|
||||
return
|
||||
|
||||
args.push_front(ctx)
|
||||
result = callv("_generate", args)
|
||||
|
||||
for i in range(inputs.size()):
|
||||
inputs[i] = []
|
||||
|
||||
func post_generate(ctx: Foliage3DExecutor.NodeContext):
|
||||
if not has_method("_post_generate"):
|
||||
return
|
||||
|
||||
call("_post_generate", ctx)
|
||||
|
||||
func type() -> String:
|
||||
return get_script().get_global_name().trim_prefix("Foliage3D")
|
||||
@@ -1 +0,0 @@
|
||||
uid://d1nqgvgqr3qao
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "slot.hpp"
|
||||
#include "terrain.hpp"
|
||||
|
||||
// #include <future>
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/variant/builtin_types.hpp>
|
||||
#include <godot_cpp/core/gdvirtual.gen.inc>
|
||||
|
||||
using namespace std;
|
||||
using namespace godot;
|
||||
|
||||
class Foliage3DNode : public Object
|
||||
{
|
||||
GDCLASS(Foliage3DNode, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
// TypedArray<Foliage3DSlot> _slots;
|
||||
TypedArray<Foliage3DNode> _inputs;
|
||||
Foliage3DTerrain* _terrain;
|
||||
|
||||
// promise<Variant>* _promise;
|
||||
// shared_future<Variant>* _result;
|
||||
Variant _result;
|
||||
|
||||
public:
|
||||
Foliage3DNode();
|
||||
~Foliage3DNode();
|
||||
|
||||
void execute();
|
||||
|
||||
virtual TypedArray<int> get_inputs();
|
||||
GDVIRTUAL0R(TypedArray<int>, get_inputs);
|
||||
|
||||
virtual TypedArray<int> get_outputs();
|
||||
GDVIRTUAL0R(TypedArray<int>, get_outputs);
|
||||
|
||||
|
||||
void set_params(Dictionary p_dict);
|
||||
// Dictionary get_params();
|
||||
|
||||
void set_input(Variant node, int slot);
|
||||
|
||||
Foliage3DTerrain* get_terrain() { return _terrain; };
|
||||
void set_terrain(Foliage3DTerrain* p_terrain) { _terrain = p_terrain; };
|
||||
|
||||
Variant get_result() { return _result; };
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "point.hpp"
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
|
||||
Foliage3DPoint::Foliage3DPoint()
|
||||
{
|
||||
}
|
||||
|
||||
// Foliage3DPoint::Foliage3DPoint(Transform3D p_transform, Vector3 p_size)
|
||||
// {
|
||||
// _transform = p_transform;
|
||||
// _size = p_size;
|
||||
// _update_aabb();
|
||||
// }
|
||||
|
||||
Foliage3DPoint::~Foliage3DPoint()
|
||||
{
|
||||
}
|
||||
|
||||
void Foliage3DPoint::set_transform(Transform3D p_transform)
|
||||
{
|
||||
_transform = p_transform;
|
||||
_update_aabb();
|
||||
}
|
||||
|
||||
void Foliage3DPoint::set_size(Vector3 p_size)
|
||||
{
|
||||
_size = p_size;
|
||||
_update_aabb();
|
||||
}
|
||||
|
||||
bool Foliage3DPoint::intersects(Foliage3DPoint* p_point)
|
||||
{
|
||||
return _aabb.intersects(p_point->_aabb);
|
||||
}
|
||||
|
||||
void Foliage3DPoint::_update_aabb()
|
||||
{
|
||||
Vector3 scaled_size = _size.operator*(_transform.basis.get_scale());
|
||||
_aabb = AABB(_transform.origin.operator-(scaled_size.operator/(2)), scaled_size);
|
||||
}
|
||||
|
||||
void Foliage3DPoint::_bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("get_transform"), &Foliage3DPoint::get_transform);
|
||||
ClassDB::bind_method(D_METHOD("set_transform", "transform"), &Foliage3DPoint::set_transform);
|
||||
ClassDB::bind_method(D_METHOD("get_size"), &Foliage3DPoint::get_size);
|
||||
ClassDB::bind_method(D_METHOD("set_size", "size"), &Foliage3DPoint::set_size);
|
||||
ClassDB::bind_method(D_METHOD("get_aabb"), &Foliage3DPoint::get_aabb);
|
||||
ClassDB::bind_method(D_METHOD("intersects", "point"), &Foliage3DPoint::intersects);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform"), "set_transform", "get_transform");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size");
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
class Foliage3DPoint : public Object
|
||||
{
|
||||
GDCLASS(Foliage3DPoint, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
Transform3D _transform;
|
||||
Vector3 _size;
|
||||
AABB _aabb;
|
||||
void _update_aabb();
|
||||
|
||||
public:
|
||||
Foliage3DPoint();
|
||||
// Foliage3DPoint(Transform3D p_transform, Vector3 p_size);
|
||||
~Foliage3DPoint();
|
||||
|
||||
Transform3D get_transform() const { return _transform; }
|
||||
void set_transform(Transform3D p_transform);
|
||||
Vector3 get_size() const { return _size; }
|
||||
void set_size(Vector3 p_size);
|
||||
AABB get_aabb() const { return _aabb; }
|
||||
bool intersects(Foliage3DPoint* p_input);
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "port.hpp"
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
Foliage3DPort::Foliage3DPort()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Foliage3DPort::~Foliage3DPort()
|
||||
{
|
||||
}
|
||||
|
||||
void Foliage3DPort::_bind_methods()
|
||||
{
|
||||
BIND_ENUM_CONSTANT(POINT);
|
||||
BIND_ENUM_CONSTANT(VECTOR3);
|
||||
BIND_ENUM_CONSTANT(FLOAT);
|
||||
BIND_ENUM_CONSTANT(INT);
|
||||
BIND_ENUM_CONSTANT(BOOL);
|
||||
BIND_ENUM_CONSTANT(STRING);
|
||||
|
||||
BIND_ENUM_CONSTANT(NONE);
|
||||
BIND_ENUM_CONSTANT(POSITION);
|
||||
BIND_ENUM_CONSTANT(ROTATION);
|
||||
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_type"), &Foliage3DPort::get_type);
|
||||
ClassDB::bind_method(D_METHOD("set_type", "type"), &Foliage3DPort::set_type);
|
||||
ClassDB::bind_method(D_METHOD("get_name"), &Foliage3DPort::get_name);
|
||||
ClassDB::bind_method(D_METHOD("set_name", "name"), &Foliage3DPort::set_name);
|
||||
ClassDB::bind_method(D_METHOD("get_hint"), &Foliage3DPort::get_hint);
|
||||
ClassDB::bind_method(D_METHOD("set_hint", "hint"), &Foliage3DPort::set_hint);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_type", "get_type");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "hint"), "set_hint", "get_hint");
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
|
||||
|
||||
#include <godot_cpp/classes/global_constants.hpp>
|
||||
#include <godot_cpp/core/binder_common.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
class Foliage3DPort : public Object
|
||||
{
|
||||
GDCLASS(Foliage3DPort, Object);
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
POINT,
|
||||
VECTOR3,
|
||||
FLOAT,
|
||||
INT,
|
||||
BOOL,
|
||||
STRING,
|
||||
};
|
||||
|
||||
enum TypeHint {
|
||||
NONE,
|
||||
POSITION,
|
||||
ROTATION
|
||||
};
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Foliage3DPort();
|
||||
~Foliage3DPort();
|
||||
|
||||
Type type;
|
||||
String name;
|
||||
TypeHint hint;
|
||||
|
||||
Type get_type() const { return type; }
|
||||
void set_type(Type p_type) { type = p_type; };
|
||||
|
||||
String get_name() const { return name; }
|
||||
void set_name(String p_name) { name = p_name; };
|
||||
|
||||
TypeHint get_hint() const { return hint; }
|
||||
void set_hint(TypeHint p_hint) { hint = p_hint; };
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(Foliage3DPort::Type);
|
||||
VARIANT_ENUM_CAST(Foliage3DPort::TypeHint);
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "prune.hpp"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
Foliage3DPrune::Foliage3DPrune()
|
||||
{
|
||||
// Foliage3DSlot* inout = memnew(Foliage3DSlot);
|
||||
// inout->left->type = Foliage3DPort::Type::POINT;
|
||||
// inout->right->type = Foliage3DPort::Type::POINT;
|
||||
|
||||
// _slots.append(inout);
|
||||
}
|
||||
|
||||
Foliage3DPrune::~Foliage3DPrune()
|
||||
{
|
||||
}
|
||||
|
||||
TypedArray<int> Foliage3DPrune::get_inputs()
|
||||
{
|
||||
TypedArray<int> inputs;
|
||||
inputs.append(Variant::Type::TRANSFORM3D);
|
||||
return inputs;
|
||||
}
|
||||
|
||||
TypedArray<int> Foliage3DPrune::get_outputs()
|
||||
{
|
||||
TypedArray<int> outputs;
|
||||
outputs.append(Variant::Type::TRANSFORM3D);
|
||||
return outputs;
|
||||
}
|
||||
|
||||
Array Foliage3DPrune::_execute(TypedArray<Foliage3DPoint> p_input)
|
||||
{
|
||||
TypedArray<Foliage3DPoint> points = p_input.duplicate();
|
||||
for (int i = p_input.size() - 1; i >= 0; i--) {
|
||||
Foliage3DPoint* p1 = cast_to<Foliage3DPoint>(p_input[i]);
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
Foliage3DPoint* p2 = cast_to<Foliage3DPoint>(p_input[j]);
|
||||
if (p1->intersects(p2))
|
||||
{
|
||||
points.remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array result;
|
||||
result.append(points);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Foliage3DPrune::_bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("_execute"), &Foliage3DPrune::_execute);
|
||||
ClassDB::bind_method(D_METHOD("get_inputs"), &Foliage3DPrune::get_inputs);
|
||||
ClassDB::bind_method(D_METHOD("get_outputs"), &Foliage3DPrune::get_outputs);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DPrune extends Foliage3DNode
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
# removes all points within the extents of self
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
var result = Foliage3DPoint.Grid.new(input).prune()
|
||||
return [result]
|
||||
@@ -1 +0,0 @@
|
||||
uid://t37le0xtvd6r
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "point.hpp"
|
||||
#include "node.hpp"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
class Foliage3DPrune : public Foliage3DNode
|
||||
{
|
||||
GDCLASS(Foliage3DPrune, Foliage3DNode);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
Array _execute(TypedArray<Foliage3DPoint> p_input);
|
||||
|
||||
public:
|
||||
Foliage3DPrune();
|
||||
~Foliage3DPrune();
|
||||
|
||||
TypedArray<int> get_inputs();
|
||||
TypedArray<int> get_outputs();
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "register_types.h"
|
||||
|
||||
#include <gdextension_interface.h>
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
|
||||
#include "point.hpp"
|
||||
#include "prune.hpp"
|
||||
#include "difference.hpp"
|
||||
#include "terrain.hpp"
|
||||
|
||||
|
||||
void gdextension_initialize(ModuleInitializationLevel p_level)
|
||||
{
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE)
|
||||
{
|
||||
ClassDB::register_class<Foliage3DTerrain>();
|
||||
ClassDB::register_class<Foliage3DPort>();
|
||||
ClassDB::register_class<Foliage3DSlot>();
|
||||
ClassDB::register_class<Foliage3DNode>();
|
||||
ClassDB::register_class<Foliage3DPoint>();
|
||||
ClassDB::register_class<Foliage3DPrune>();
|
||||
ClassDB::register_class<Foliage3DDifference>();
|
||||
}
|
||||
}
|
||||
|
||||
void gdextension_terminate(ModuleInitializationLevel p_level)
|
||||
{
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
GDExtensionBool GDE_EXPORT gdextension_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization)
|
||||
{
|
||||
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
|
||||
|
||||
init_obj.register_initializer(gdextension_initialize);
|
||||
init_obj.register_terminator(gdextension_terminate);
|
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
|
||||
return init_obj.init();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
void gdextension_initialize(godot::ModuleInitializationLevel p_level);
|
||||
void gdextension_terminate(godot::ModuleInitializationLevel p_level);
|
||||
@@ -1,40 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DSlice extends Foliage3DNode
|
||||
|
||||
@export_range(0.0, 1.0) var weight: float = 0.5:
|
||||
set(value):
|
||||
weight = value
|
||||
changed.emit()
|
||||
|
||||
@export var randomize: bool = true:
|
||||
set(value):
|
||||
randomize = value
|
||||
changed.emit()
|
||||
|
||||
@export var seed: int:
|
||||
set(value):
|
||||
seed = value
|
||||
changed.emit()
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT, TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
var result = input.duplicate()
|
||||
if randomize:
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
array_shuffle(result, rng)
|
||||
|
||||
var index = roundi(remap(weight, 0.0, 1.0, 0, input.size()))
|
||||
return [result.slice(0, index), result.slice(index, result.size())]
|
||||
|
||||
func array_shuffle(array: Array, rng: RandomNumberGenerator) -> void:
|
||||
for i in range(array.size() - 2):
|
||||
var j := rng.randi_range(i, array.size() - 1)
|
||||
var tmp = array[i]
|
||||
array[i] = array[j]
|
||||
array[j] = tmp
|
||||
@@ -1 +0,0 @@
|
||||
uid://bqn17g3wl2nee
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "slot.hpp"
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
Foliage3DSlot::Foliage3DSlot()
|
||||
{
|
||||
left = memnew(Foliage3DPort);
|
||||
right = memnew(Foliage3DPort);
|
||||
}
|
||||
|
||||
|
||||
Foliage3DSlot::~Foliage3DSlot()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void Foliage3DSlot::_bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("get_prop"), &Foliage3DSlot::get_prop);
|
||||
ClassDB::bind_method(D_METHOD("set_prop", "prop"), &Foliage3DSlot::set_prop);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_name"), &Foliage3DSlot::get_name);
|
||||
ClassDB::bind_method(D_METHOD("set_name", "name"), &Foliage3DSlot::set_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_left"), &Foliage3DSlot::get_left);
|
||||
ClassDB::bind_method(D_METHOD("set_left", "slot"), &Foliage3DSlot::set_left);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_right"), &Foliage3DSlot::get_right);
|
||||
ClassDB::bind_method(D_METHOD("set_right", "slot"), &Foliage3DSlot::set_right);
|
||||
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "prop"), "set_prop", "get_prop");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_name", "get_name");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "left"), "set_left", "get_left");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right"), "set_right", "get_right");
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "port.hpp"
|
||||
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
|
||||
class Foliage3DSlot : public Object
|
||||
{
|
||||
GDCLASS(Foliage3DSlot, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Foliage3DSlot();
|
||||
~Foliage3DSlot();
|
||||
|
||||
String prop;
|
||||
String name;
|
||||
Foliage3DPort *left;
|
||||
Foliage3DPort *right;
|
||||
|
||||
String get_prop() const { return prop; }
|
||||
void set_prop(String p_prop) { prop = p_prop; };
|
||||
|
||||
|
||||
String get_name() const { return name; }
|
||||
void set_name(String p_name) { name = p_name; };
|
||||
|
||||
Foliage3DPort* get_left() const { return left; }
|
||||
void set_left(Foliage3DPort *p_left) { left = p_left; };
|
||||
|
||||
|
||||
Foliage3DPort* get_right() const { return right; }
|
||||
void set_right(Foliage3DPort *p_right) { right = p_right; };
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
@tool
|
||||
class_name Foliage3DSurfaceSampler extends Foliage3DNode
|
||||
|
||||
@export var spacing: float = 10.0:
|
||||
set(value):
|
||||
spacing = value
|
||||
changed.emit()
|
||||
|
||||
@export var point_extents: Vector3 = Vector3(1, 1, 1):
|
||||
set(value):
|
||||
point_extents = value
|
||||
changed.emit()
|
||||
|
||||
@export var seed: int:
|
||||
set(value):
|
||||
seed = value
|
||||
changed.emit()
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return []
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext) -> Array:
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
|
||||
var points: Array[Foliage3DPoint]
|
||||
var size = ctx.bounds.aabb.size
|
||||
var x = 0.0
|
||||
while x < size.x:
|
||||
x += spacing
|
||||
var z = 0.0
|
||||
while z < size.z:
|
||||
z += spacing
|
||||
var offset = Vector3(rng.randf_range(-spacing/2, spacing/2), 0, rng.randf_range(-spacing/2, spacing/2))
|
||||
var position = Vector3(x, 0, z) + ctx.bounds.aabb.position + offset
|
||||
position.y = ctx.with_terrain(func(t: Terrain3D): return t.data.get_height(position))
|
||||
|
||||
if is_nan(position.y):
|
||||
position.y = 0
|
||||
|
||||
if not ctx.bounds.contains(position):
|
||||
continue
|
||||
|
||||
var normal = ctx.with_terrain(func(t: Terrain3D): return t.data.get_normal(position))
|
||||
if is_nan(normal.x):
|
||||
normal = Vector3.UP
|
||||
|
||||
var basis: Basis
|
||||
if normal != Vector3.UP:
|
||||
var tangent = normal.cross(Vector3.UP).normalized()
|
||||
var bitangent = tangent.cross(normal).normalized()
|
||||
basis = Basis(tangent, normal, bitangent)
|
||||
|
||||
var transform = Transform3D(basis, position)
|
||||
points.append(Foliage3DPoint.new(transform, point_extents))
|
||||
|
||||
return [points]
|
||||
@@ -1 +0,0 @@
|
||||
uid://cc4n4ipq4i7jk
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "terrain.hpp"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
Foliage3DTerrain::Foliage3DTerrain()
|
||||
{
|
||||
}
|
||||
|
||||
Foliage3DTerrain::~Foliage3DTerrain()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
double Foliage3DTerrain::get_height(Vector3 p_position)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Vector3 Foliage3DTerrain::get_normal(Vector3 p_position)
|
||||
{
|
||||
return Vector3();
|
||||
}
|
||||
|
||||
void Foliage3DTerrain::add_mesh_xforms(int id, TypedArray<Transform3D> xforms)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Foliage3DTerrain::_bind_methods()
|
||||
{
|
||||
ClassDB::bind_method(D_METHOD("get_height", "position"), &Foliage3DTerrain::get_height);
|
||||
ClassDB::bind_method(D_METHOD("get_normal", "position"), &Foliage3DTerrain::get_normal);
|
||||
|
||||
GDVIRTUAL_BIND(get_height, "position");
|
||||
GDVIRTUAL_BIND(get_normal, "position");
|
||||
GDVIRTUAL_BIND(add_mesh_xforms, "id", "xforms");
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/core/gdvirtual.gen.inc>
|
||||
#include <godot_cpp/core/binder_common.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
class Foliage3DTerrain : public Object
|
||||
{
|
||||
GDCLASS(Foliage3DTerrain, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Foliage3DTerrain();
|
||||
~Foliage3DTerrain();
|
||||
|
||||
double get_height(Vector3 p_position);
|
||||
GDVIRTUAL1R(double, get_height, Vector3);
|
||||
|
||||
Vector3 get_normal(Vector3 p_position);
|
||||
GDVIRTUAL1R(Vector3, get_normal, Vector3);
|
||||
|
||||
void add_mesh_xforms(int id, TypedArray<Transform3D> xforms);
|
||||
GDVIRTUAL2(add_mesh_xforms, int, TypedArray<Transform3D>);
|
||||
};
|
||||
@@ -1,118 +0,0 @@
|
||||
class_name Foliage3DTexturePainter extends Foliage3DNode
|
||||
|
||||
const BRUSH_PATH: String = "res://addons/terrain_3d/brushes"
|
||||
|
||||
enum Brush {
|
||||
CIRCLE0,
|
||||
CIRCLE1,
|
||||
CIRCLE2,
|
||||
CIRCLE3,
|
||||
CIRCLE4,
|
||||
SQUARE1,
|
||||
SQUARE2,
|
||||
SQUARE3,
|
||||
SQUARE4,
|
||||
SQUARE5,
|
||||
}
|
||||
|
||||
@export var albedo_texture: Texture2D:
|
||||
set(value):
|
||||
albedo_texture = value
|
||||
changed.emit()
|
||||
|
||||
@export var brush: Brush:
|
||||
set(value):
|
||||
brush = value
|
||||
changed.emit()
|
||||
|
||||
@export_range(0.1, 1000.0) var strength: float = 1000.0:
|
||||
set(value):
|
||||
strength = value
|
||||
changed.emit()
|
||||
|
||||
# textures are painted in order of priority, lowest first, highest last
|
||||
@export_range(0, 1024) var priority: int:
|
||||
set(value):
|
||||
priority = value
|
||||
changed.emit()
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
if input.is_empty() or albedo_texture == null:
|
||||
return []
|
||||
|
||||
if priority > 0:
|
||||
ctx.mutate(func(data: Dictionary):
|
||||
data[self] = input
|
||||
)
|
||||
else:
|
||||
ctx.with_terrain_main(paint.bind(input))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
func _post_generate(ctx: Foliage3DExecutor.NodeContext):
|
||||
var data = ctx.finalize()
|
||||
if data is not Dictionary:
|
||||
return
|
||||
|
||||
var keys: Array = data.keys()
|
||||
keys.sort_custom(func(a, b): return a.priority < b.priority)
|
||||
|
||||
ctx.with_terrain_main(func(terrain: Terrain3D):
|
||||
for node: Foliage3DTexturePainter in keys:
|
||||
node.paint(terrain, data[node])
|
||||
)
|
||||
|
||||
func paint(terrain: Terrain3D, input: Array[Foliage3DPoint]):
|
||||
var editor = Terrain3DEditor.new()
|
||||
editor.set_terrain(terrain)
|
||||
editor.set_tool(Terrain3DEditor.TEXTURE)
|
||||
editor.set_operation(Terrain3DEditor.REPLACE)
|
||||
|
||||
var brush = load_brush(get_or_create_texture(terrain))
|
||||
editor.set_brush_data(brush)
|
||||
|
||||
editor.start_operation(Vector3.ZERO)
|
||||
for point: Foliage3DPoint in input:
|
||||
var size = point.size.x + point.size.z
|
||||
if brush["size"] != size:
|
||||
brush["size"] = size
|
||||
editor.set_brush_data(brush)
|
||||
editor.operate(point.transform.origin, 0.0)
|
||||
editor.stop_operation()
|
||||
|
||||
func get_or_create_texture(terrain: Terrain3D) -> int:
|
||||
var texture_count = terrain.assets.get_texture_count()
|
||||
for i in range(texture_count):
|
||||
var texture = terrain.assets.get_texture(i)
|
||||
if texture.albedo_texture.resource_path == albedo_texture.resource_path:
|
||||
return i
|
||||
|
||||
var texture = Terrain3DTextureAsset.new()
|
||||
texture.id = texture_count
|
||||
texture.albedo_texture = albedo_texture
|
||||
texture.name = albedo_texture.resource_path.get_file()
|
||||
terrain.assets.set_texture(texture_count, texture)
|
||||
return texture_count
|
||||
|
||||
func load_brush(texture_id: int) -> Dictionary:
|
||||
var img: Image = Image.load_from_file("%s/%s.exr" % [BRUSH_PATH, Brush.keys()[brush].to_lower()])
|
||||
var thumbimg: Image = img.duplicate()
|
||||
img.convert(Image.FORMAT_RF)
|
||||
|
||||
var tex_img = img
|
||||
if tex_img.get_width() < 1024 and tex_img.get_height() < 1024:
|
||||
tex_img = tex_img.duplicate()
|
||||
tex_img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
|
||||
var tex: ImageTexture = ImageTexture.create_from_image(tex_img)
|
||||
|
||||
return {
|
||||
"brush": [img, tex],
|
||||
"strength": strength,
|
||||
"enable_texture": true,
|
||||
"asset_id": texture_id,
|
||||
"size": 0.0,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://ouxsf8xl5t6t
|
||||
@@ -1,85 +0,0 @@
|
||||
class_name Foliage3DTransform extends Foliage3DNode
|
||||
|
||||
@export var offset_min: Vector3:
|
||||
set(value):
|
||||
offset_min = value
|
||||
changed.emit()
|
||||
@export var offset_max: Vector3:
|
||||
set(value):
|
||||
offset_max = value
|
||||
changed.emit()
|
||||
@export var rotation_min: Vector3:
|
||||
set(value):
|
||||
rotation_min = value
|
||||
changed.emit()
|
||||
@export var rotation_max: Vector3:
|
||||
set(value):
|
||||
rotation_max = value
|
||||
changed.emit()
|
||||
@export var rotation_absolute: bool:
|
||||
set(value):
|
||||
rotation_absolute = value
|
||||
changed.emit()
|
||||
@export var scale_min: Vector3 = Vector3(1, 1, 1):
|
||||
set(value):
|
||||
scale_min = value
|
||||
changed.emit()
|
||||
@export var scale_max: Vector3 = Vector3(1, 1, 1):
|
||||
set(value):
|
||||
scale_max = value
|
||||
changed.emit()
|
||||
@export var scale_uniform: bool = true:
|
||||
set(value):
|
||||
scale_uniform = value
|
||||
changed.emit()
|
||||
@export var seed: int:
|
||||
set(value):
|
||||
seed = value
|
||||
changed.emit()
|
||||
|
||||
func get_inputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func get_outputs() -> Array[int]:
|
||||
return [TYPE_POINT]
|
||||
|
||||
func _generate(ctx: Foliage3DExecutor.NodeContext, input: Array[Foliage3DPoint]) -> Array:
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
|
||||
var result: Array[Foliage3DPoint]
|
||||
result.assign(input.map(func(p): return p.duplicate()))
|
||||
|
||||
if offset_min != Vector3.ZERO or offset_max != Vector3.ZERO:
|
||||
for i in range(result.size()):
|
||||
var transform = result[i].transform
|
||||
transform.origin.x += rng.randf_range(offset_min.x, offset_max.x)
|
||||
transform.origin.y += rng.randf_range(offset_min.y, offset_max.y)
|
||||
transform.origin.z += rng.randf_range(offset_min.z, offset_max.z)
|
||||
result[i].transform = transform
|
||||
|
||||
if rotation_min != Vector3.ZERO or rotation_max != Vector3.ZERO or rotation_absolute:
|
||||
for i in range(result.size()):
|
||||
var transform = result[i].transform
|
||||
if rotation_absolute:
|
||||
transform.basis = Basis()
|
||||
transform.basis = transform.basis * Basis.from_euler(Vector3(
|
||||
deg_to_rad(rng.randf_range(rotation_min.x, rotation_max.x)),
|
||||
deg_to_rad(rng.randf_range(rotation_min.y, rotation_max.y)),
|
||||
deg_to_rad(rng.randf_range(rotation_min.z, rotation_max.z))
|
||||
))
|
||||
result[i].transform = transform
|
||||
|
||||
if not scale_min.is_equal_approx(scale_max):
|
||||
for i in range(result.size()):
|
||||
var transform = result[i].transform
|
||||
var scale : Vector3
|
||||
if scale_uniform:
|
||||
var val = rng.randf_range(scale_min.x, scale_max.x)
|
||||
scale = Vector3(val, val, val)
|
||||
else:
|
||||
scale = Vector3(rng.randf_range(scale_min.x, scale_max.x), rng.randf_range(scale_min.y, scale_max.y), rng.randf_range(scale_min.z, scale_max.z))
|
||||
transform.basis = transform.basis.scaled(scale)
|
||||
result[i].transform = transform
|
||||
|
||||
return [result]
|
||||
@@ -1 +0,0 @@
|
||||
uid://sf4o83hyhd1p
|
||||
@@ -1,46 +0,0 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
#var inspector_plugin = Foliage3DEditorInspectorPlugin.new()
|
||||
|
||||
var container: BoxContainer = BoxContainer.new()
|
||||
var button: Button
|
||||
|
||||
@onready var selection = EditorInterface.get_selection()
|
||||
var current: Foliage3D
|
||||
|
||||
func _ready():
|
||||
#add_inspector_plugin(inspector_plugin)
|
||||
selection.selection_changed.connect(_selection_changed)
|
||||
_selection_changed()
|
||||
|
||||
container.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
container.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||
|
||||
button = add_control_to_bottom_panel(container, "Foliage3D")
|
||||
add_custom_type("Foliage3D", "Node3D", Foliage3D, null)
|
||||
|
||||
func _exit_tree():
|
||||
#remove_inspector_plugin(inspector_plugin)
|
||||
remove_control_from_bottom_panel(container)
|
||||
remove_custom_type("Foliage3D")
|
||||
selection.selection_changed.disconnect(_selection_changed)
|
||||
button.queue_free()
|
||||
|
||||
|
||||
func _selection_changed():
|
||||
var nodes = selection.get_selected_nodes()
|
||||
if nodes.is_empty():
|
||||
return
|
||||
var foliage = nodes[0]
|
||||
if foliage is Foliage3D:
|
||||
if not foliage.graph_changed.is_connected(add_graph_edit):
|
||||
foliage.graph_changed.connect(add_graph_edit.bind(foliage))
|
||||
add_graph_edit(foliage)
|
||||
|
||||
func add_graph_edit(foliage: Foliage3D):
|
||||
for child in container.get_children():
|
||||
container.remove_child(child)
|
||||
child.queue_free()
|
||||
if is_instance_valid(foliage.graph) and is_instance_valid(foliage.terrain):
|
||||
container.add_child(foliage.create_edit())
|
||||
@@ -1 +0,0 @@
|
||||
uid://bbkd83wq3l2pe
|
||||
@@ -1,109 +0,0 @@
|
||||
class_name Foliage3DPoint
|
||||
|
||||
var transform: Transform3D:
|
||||
set(value):
|
||||
transform = value
|
||||
update_aabb()
|
||||
|
||||
var extents: Vector3:
|
||||
set(value):
|
||||
extents = value
|
||||
size = extents * 2
|
||||
update_aabb()
|
||||
|
||||
var size: Vector3
|
||||
|
||||
var density: float
|
||||
|
||||
var aabb: AABB
|
||||
|
||||
func _init(p_transform: Transform3D, p_extents: Vector3, p_density = 0.0):
|
||||
transform = p_transform
|
||||
extents = p_extents
|
||||
density = p_density
|
||||
|
||||
func update_aabb():
|
||||
var scaled_size = size * transform.basis.get_scale()
|
||||
aabb = AABB(transform.origin - scaled_size/2, scaled_size)
|
||||
|
||||
func intersects(point: Foliage3DPoint) -> bool:
|
||||
return aabb.intersects(point.aabb)
|
||||
|
||||
func duplicate() -> Foliage3DPoint:
|
||||
return Foliage3DPoint.new(transform, extents)
|
||||
|
||||
|
||||
class Grid:
|
||||
var points: Array[Foliage3DPoint]
|
||||
var grid: Dictionary[Vector3i, Array]
|
||||
var cell_size: float = INF
|
||||
|
||||
func _init(p_points: Array[Foliage3DPoint]):
|
||||
points = p_points
|
||||
for point in points:
|
||||
cell_size = min(cell_size, min(point.aabb.size.x, point.aabb.size.y, point.aabb.size.z))
|
||||
cell_size = max(cell_size, 0.001) # Prevent division by zero
|
||||
|
||||
func get_grid_keys(point: Foliage3DPoint) -> Array:
|
||||
var min_cell := Vector3i(point.aabb.position / cell_size)
|
||||
var max_cell := Vector3i((point.aabb.position + point.aabb.size) / cell_size)
|
||||
var keys = []
|
||||
for x in range(min_cell.x, max_cell.x + 1):
|
||||
for y in range(min_cell.y, max_cell.y + 1):
|
||||
for z in range(min_cell.z, max_cell.z + 1):
|
||||
keys.append(Vector3i(x, y, z))
|
||||
return keys
|
||||
|
||||
func difference(input: Array[Foliage3DPoint]) -> Array[Foliage3DPoint]:
|
||||
for point in points:
|
||||
for key in get_grid_keys(point):
|
||||
if key not in grid:
|
||||
grid[key] = []
|
||||
grid[key].append(point)
|
||||
|
||||
var result: Array[Foliage3DPoint]
|
||||
for point in input:
|
||||
var overlaps := false
|
||||
for key in get_grid_keys(point):
|
||||
if key in grid:
|
||||
for diff_point in grid[key]:
|
||||
if point.intersects(diff_point):
|
||||
overlaps = true
|
||||
break
|
||||
if overlaps:
|
||||
break
|
||||
if not overlaps:
|
||||
result.append(point)
|
||||
return result
|
||||
|
||||
func prune() -> Array[Foliage3DPoint]:
|
||||
var result: Array[Foliage3DPoint]
|
||||
var to_remove := {}
|
||||
|
||||
for point in points:
|
||||
var keys = get_grid_keys(point)
|
||||
var overlapping = false
|
||||
|
||||
# Check if the box overlaps with any existing boxes in the same grid cell
|
||||
for key in keys:
|
||||
if key in grid:
|
||||
for other in grid[key]:
|
||||
if point.intersects(other):
|
||||
overlapping = true
|
||||
to_remove[other] = true
|
||||
break
|
||||
if overlapping:
|
||||
break
|
||||
|
||||
if not overlapping:
|
||||
for key in keys:
|
||||
if key not in grid:
|
||||
grid[key] = []
|
||||
grid[key].append(point)
|
||||
|
||||
# Filter out overlapping boxes
|
||||
for point in points:
|
||||
if point not in to_remove:
|
||||
result.append(point)
|
||||
|
||||
return result
|
||||
@@ -1 +0,0 @@
|
||||
uid://c8d1f38wya20c
|
||||
@@ -1,98 +0,0 @@
|
||||
[configuration]
|
||||
entry_symbol = "gdext_rust_init"
|
||||
compatibility_minimum = 4.5
|
||||
reloadable = true
|
||||
|
||||
[libraries]
|
||||
linux.x86_64 = "bin/x86_64-unknown-linux-gnu/libgdcef.so"
|
||||
linux.arm64 = "bin/aarch64-unknown-linux-gnu/libgdcef.so"
|
||||
macos = "bin/universal-apple-darwin/Godot CEF.framework"
|
||||
windows.x86_64 = "bin/x86_64-pc-windows-msvc/gdcef.dll"
|
||||
windows.arm64 = "bin/aarch64-pc-windows-msvc/gdcef.dll"
|
||||
|
||||
[icons]
|
||||
CefTexture = "icons/webview.png"
|
||||
CefIpcInspector = "icons/webview.png"
|
||||
|
||||
[dependencies]
|
||||
|
||||
windows.x86_64 = {
|
||||
"bin/x86_64-pc-windows-msvc/gdcef.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/gdcef_helper.exe" : "",
|
||||
|
||||
"bin/x86_64-pc-windows-msvc/locales" : "",
|
||||
"bin/x86_64-pc-windows-msvc/bootstrap.exe" : "",
|
||||
"bin/x86_64-pc-windows-msvc/bootstrapc.exe" : "",
|
||||
"bin/x86_64-pc-windows-msvc/chrome_100_percent.pak" : "",
|
||||
"bin/x86_64-pc-windows-msvc/chrome_200_percent.pak" : "",
|
||||
"bin/x86_64-pc-windows-msvc/chrome_elf.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/d3dcompiler_47.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/dxcompiler.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/dxil.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/icudtl.dat" : "",
|
||||
"bin/x86_64-pc-windows-msvc/libcef.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/libEGL.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/libGLESv2.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/resources.pak" : "",
|
||||
"bin/x86_64-pc-windows-msvc/v8_context_snapshot.bin" : "",
|
||||
"bin/x86_64-pc-windows-msvc/vk_swiftshader.dll" : "",
|
||||
"bin/x86_64-pc-windows-msvc/vk_swiftshader_icd.json" : "",
|
||||
"bin/x86_64-pc-windows-msvc/vulkan-1.dll" : ""
|
||||
}
|
||||
|
||||
windows.arm64 = {
|
||||
"bin/aarch64-pc-windows-msvc/gdcef.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/gdcef_helper.exe" : "",
|
||||
|
||||
"bin/aarch64-pc-windows-msvc/locales" : "",
|
||||
"bin/aarch64-pc-windows-msvc/bootstrap.exe" : "",
|
||||
"bin/aarch64-pc-windows-msvc/bootstrapc.exe" : "",
|
||||
"bin/aarch64-pc-windows-msvc/chrome_100_percent.pak" : "",
|
||||
"bin/aarch64-pc-windows-msvc/chrome_200_percent.pak" : "",
|
||||
"bin/aarch64-pc-windows-msvc/chrome_elf.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/d3dcompiler_47.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/dxcompiler.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/dxil.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/icudtl.dat" : "",
|
||||
"bin/aarch64-pc-windows-msvc/libcef.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/libEGL.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/libGLESv2.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/resources.pak" : "",
|
||||
"bin/aarch64-pc-windows-msvc/v8_context_snapshot.bin" : "",
|
||||
"bin/aarch64-pc-windows-msvc/vk_swiftshader.dll" : "",
|
||||
"bin/aarch64-pc-windows-msvc/vk_swiftshader_icd.json" : "",
|
||||
"bin/aarch64-pc-windows-msvc/vulkan-1.dll" : ""
|
||||
}
|
||||
|
||||
macos = {
|
||||
"bin/universal-apple-darwin/Godot CEF.framework" : "Contents/Frameworks",
|
||||
"bin/universal-apple-darwin/Godot CEF.app" : "Contents/Frameworks"
|
||||
}
|
||||
|
||||
linux.x86_64 = {
|
||||
"bin/x86_64-unknown-linux-gnu/libgdcef.so" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/gdcef_helper" : "",
|
||||
|
||||
"bin/x86_64-unknown-linux-gnu/chrome-sandbox" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/libcef.so" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/libEGL.so" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/libGLESv2.so" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/libvk_swiftshader.so" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/libvulkan.so.1" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/v8_context_snapshot.bin" : "",
|
||||
"bin/x86_64-unknown-linux-gnu/vk_swiftshader_icd.json" : ""
|
||||
}
|
||||
|
||||
linux.arm64 = {
|
||||
"bin/aarch64-unknown-linux-gnu/libgdcef.so" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/gdcef_helper" : "",
|
||||
|
||||
"bin/aarch64-unknown-linux-gnu/chrome-sandbox" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/libcef.so" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/libEGL.so" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/libGLESv2.so" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/libvk_swiftshader.so" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/libvulkan.so.1" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/v8_context_snapshot.bin" : "",
|
||||
"bin/aarch64-unknown-linux-gnu/vk_swiftshader_icd.json" : ""
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://cs3wfn3u42rgo
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 826 KiB |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://1axq0kdirfs5"
|
||||
path="res://.godot/imported/webview.png-257894f448b3347f6b7b31ad54724fa2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot_cef/icons/webview.png"
|
||||
dest_files=["res://.godot/imported/webview.png-257894f448b3347f6b7b31ad54724fa2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -1,14 +0,0 @@
|
||||
[configuration]
|
||||
entry_symbol = "gdext_rust_init"
|
||||
compatibility_minimum = 4.6
|
||||
reloadable = true
|
||||
|
||||
[libraries]
|
||||
linux.debug.x86_64 = "res://../libpainless/target/debug/libpainless.so"
|
||||
linux.release.x86_64 = "res://../libpainless/target/release/libpainless.so"
|
||||
|
||||
windows.debug.x86_64 = "res://../libpainless/target/debug/painless.dll"
|
||||
windows.release.x86_64 = "res://../libpainless/target/release/painless.dll"
|
||||
|
||||
macos.debug = "res://../libpainless/target/debug/libpainless.dylib"
|
||||
macos.release = "res://../libpainless/target/release/libpainless.dylib"
|
||||
@@ -1 +0,0 @@
|
||||
uid://bu8a1sxlivkaf
|
||||
@@ -1,26 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023-2025 Cory Petkovsek and Contributors
|
||||
Copyright (c) 2021 J. Cuéllar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
----
|
||||
|
||||
Third party assets have their own licenses found under assets/thirdparty.
|
||||
@@ -1,3 +0,0 @@
|
||||
# Sky3D
|
||||
|
||||
See the README in the code repository at https://github.com/TokisanGames/Sky3D.
|
||||
@@ -1,8 +0,0 @@
|
||||
## Attribution for Third Party Assets
|
||||
|
||||
`Milkyway.jpg` "[The Milky Way panorama](https://www.eso.org/public/images/eso0932a/)" by ESO/S. Brunier, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/
|
||||
).
|
||||
[Read more](/addons/sky_3d/assets/thirdparty/textures/milkyway/LICENSE.md)
|
||||
|
||||
`MoonMap.png` Copyright (c) 2019 GPoSM, MIT License
|
||||
[Read more](/addons/sky_3d/assets/thirdparty/textures/moon/LICENSE.md)
|
||||
@@ -1,7 +0,0 @@
|
||||
[gd_resource type="NoiseTexture2D" load_steps=2 format=3 uid="uid://cfqk60lpl5ljv"]
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="1"]
|
||||
|
||||
[resource]
|
||||
seamless = true
|
||||
noise = SubResource("1")
|
||||
@@ -1,5 +0,0 @@
|
||||
[gd_resource type="Curve" format=3 uid="uid://70482fhm3qg7"]
|
||||
|
||||
[resource]
|
||||
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.617886, 0), 0.0, 0.0467088, 1, 0, Vector2(0.699187, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
|
||||
point_count = 4
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 860 B |
@@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bwooawylkf7f2"
|
||||
path="res://.godot/imported/SkyIcon.png-7b3cbfa68354965cdbfdba8b95dacf67.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sky_3d/assets/textures/SkyIcon.png"
|
||||
dest_files=["res://.godot/imported/SkyIcon.png-7b3cbfa68354965cdbfdba8b95dacf67.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,41 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://djpfuyxkryegn"
|
||||
path.s3tc="res://.godot/imported/noise.jpg-b47d5d36d9abfdaec2a09c140a78ba24.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sky_3d/assets/textures/noise.jpg"
|
||||
dest_files=["res://.godot/imported/noise.jpg-b47d5d36d9abfdaec2a09c140a78ba24.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB |
@@ -1,41 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bd7u40ws8s57f"
|
||||
path.s3tc="res://.godot/imported/noise2.png-37a5cbd5451b4b9c2c18ab32099e6965.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sky_3d/assets/textures/noise2.png"
|
||||
dest_files=["res://.godot/imported/noise2.png-37a5cbd5451b4b9c2c18ab32099e6965.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 123 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user