From 1d5f9eb0f021f0f5534c24845762756d3c0e47f7 Mon Sep 17 00:00:00 2001 From: Simon Tippe Date: Sun, 18 Feb 2024 19:09:41 +0100 Subject: [PATCH] Editor improvements (#51) * Support for ray casting against scene * Added text component * Added material edit panel * Fixed a bunch of issues * Many adjustments and bug fixes * Forgot to save files * Improved content browser * New icons * Some fixes * Added "light" mode * Make content browser fully functioning * Light component panel can be used to change the shadow now * Saved scene window settings * Fix build issues * Fix code smell --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- data/editor/icons/arrowLeft.png | Bin 0 -> 1343 bytes data/editor/icons/arrowLeft.svg | 1 + data/editor/icons/arrowLeft_light.png | Bin 0 -> 1327 bytes data/editor/icons/arrowRight.png | Bin 0 -> 1367 bytes data/editor/icons/arrowRight.svg | 1 + data/editor/icons/arrowRight_light.png | Bin 0 -> 1324 bytes data/editor/icons/arrowUp.png | Bin 0 -> 1541 bytes data/editor/icons/arrowUp.svg | 1 + data/editor/icons/arrowUp_light.png | Bin 0 -> 1467 bytes data/editor/icons/audio_light.png | Bin 0 -> 3859 bytes data/editor/icons/camera_light.png | Bin 0 -> 4235 bytes data/editor/icons/document_light.png | Bin 0 -> 1862 bytes data/editor/icons/folder_light.png | Bin 0 -> 1864 bytes data/editor/icons/font.png | Bin 0 -> 1125 bytes data/editor/icons/font.svg | 1 + data/editor/icons/font_light.png | Bin 0 -> 1103 bytes data/editor/icons/image_light.png | Bin 0 -> 2449 bytes data/editor/icons/mesh_light.png | Bin 0 -> 4345 bytes data/editor/icons/play_light.png | Bin 0 -> 2291 bytes data/editor/icons/scene_light.png | Bin 0 -> 3914 bytes data/editor/icons/settings.png | Bin 0 -> 7374 bytes data/editor/icons/settings.svg | 1 + data/editor/icons/settings_light.png | Bin 0 -> 6386 bytes data/editor/icons/stop_light.png | Bin 0 -> 1004 bytes data/editor/icons/terrain_light.png | Bin 0 -> 2518 bytes data/scenes/sponza.aescene | 2 +- data/shader/deferred/direct.csh | 1 - data/shader/postprocessing.vsh | 8 +- data/shader/ssgi/temporal.csh | 6 +- data/shader/text.fsh | 72 +++- data/shader/text.vsh | 65 +++- data/shader/volumetric/volumetric.csh | 7 +- libs/ImguiExtension/Panels.h | 3 + libs/ImguiExtension/UiElements.cpp | 18 + libs/ImguiExtension/UiElements.h | 19 + libs/ImguiExtension/panels/MaterialPanel.cpp | 50 +++ libs/ImguiExtension/panels/MaterialPanel.h | 18 + libs/ImguiExtension/panels/MaterialsPanel.cpp | 44 +++ libs/ImguiExtension/panels/MaterialsPanel.h | 25 ++ libs/ImguiExtension/panels/Panel.h | 2 + libs/ImguiExtension/panels/SSGIPanel.cpp | 21 + libs/ImguiExtension/panels/SSGIPanel.h | 18 + libs/ImguiExtension/panels/SSSPanel.cpp | 18 + libs/ImguiExtension/panels/SSSPanel.h | 18 + src/demo/App.cpp | 80 ++-- src/demo/App.h | 5 +- src/editor/App.cpp | 82 ++-- src/editor/App.h | 2 +- src/editor/Config.h | 1 + src/editor/DataCreator.cpp | 3 +- src/editor/FileImporter.cpp | 35 +- src/editor/FileImporter.h | 97 ++++- src/editor/Icons.cpp | 66 +++- src/editor/Icons.h | 11 +- src/editor/Serializer.cpp | 122 ++++-- src/editor/Serializer.h | 12 + src/editor/tools/PrimitiveBatchWrapper.cpp | 50 +++ src/editor/tools/PrimitiveBatchWrapper.h | 5 + src/editor/tools/ResourcePayloadHelper.h | 45 +++ .../ui/panels/EntityPropertiesPanel.cpp | 36 +- src/editor/ui/panels/EntityPropertiesPanel.h | 4 + src/editor/ui/panels/SceneHierarchyPanel.cpp | 11 + src/editor/ui/panels/SceneHierarchyPanel.h | 4 + src/editor/ui/panels/ScenePropertiesPanel.h | 10 + .../panels/components/AudioComponentPanel.cpp | 38 ++ .../panels/components/AudioComponentPanel.h | 19 + .../components/AudioVolumeComponentPanel.cpp | 25 +- .../panels/components/CameraComponentPanel.h | 5 - .../panels/components/LightComponentPanel.cpp | 79 +++- .../panels/components/MeshComponentPanel.cpp | 23 +- .../ui/panels/components/MeshComponentPanel.h | 5 + .../components/PlayerComponentPanel.cpp | 2 + .../panels/components/TextComponentPanel.cpp | 43 +++ .../ui/panels/components/TextComponentPanel.h | 20 + .../ui/windows/ContentBrowserWindow.cpp | 301 ++++++++++++++- src/editor/ui/windows/ContentBrowserWindow.h | 67 +--- src/editor/ui/windows/LogWindow.cpp | 8 +- src/editor/ui/windows/LogWindow.h | 3 + src/editor/ui/windows/SceneWindow.cpp | 133 +++++-- src/editor/ui/windows/SceneWindow.h | 3 + src/engine/Engine.cpp | 2 +- src/engine/Font.cpp | 2 +- src/engine/Font.h | 2 +- src/engine/Viewport.cpp | 12 +- src/engine/audio/AudioSerializer.cpp | 41 ++ src/engine/audio/AudioSerializer.h | 36 +- src/engine/audio/AudioStream.cpp | 4 +- src/engine/common/Path.cpp | 3 + src/engine/common/SerializationHelper.cpp | 14 + src/engine/common/SerializationHelper.h | 4 + src/engine/input/Keyboard.cpp | 2 +- src/engine/lighting/LightingSerializer.cpp | 363 +++++++++++++++++ src/engine/lighting/LightingSerializer.h | 364 ++---------------- src/engine/lighting/Reflection.h | 2 +- src/engine/lighting/Shadow.h | 5 +- src/engine/loader/AssetLoader.cpp | 6 + src/engine/loader/AssetLoader.h | 5 + src/engine/physics/Body.cpp | 7 + src/engine/physics/Body.h | 2 + src/engine/physics/PhysicsSerializer.cpp | 266 +++++++++++++ src/engine/physics/PhysicsSerializer.h | 262 +------------ src/engine/physics/PhysicsWorld.cpp | 48 ++- src/engine/physics/PhysicsWorld.h | 7 +- src/engine/renderer/DirectLightRenderer.cpp | 1 + src/engine/renderer/MainRenderer.cpp | 12 +- src/engine/renderer/MainRenderer.h | 2 + src/engine/renderer/PostProcessRenderer.cpp | 3 + src/engine/renderer/ShadowRenderer.cpp | 4 +- src/engine/renderer/TextRenderer.cpp | 136 ++++++- src/engine/renderer/TextRenderer.h | 29 +- src/engine/renderer/VolumetricRenderer.cpp | 38 +- src/engine/scene/EntitySerializer.cpp | 119 ++++++ src/engine/scene/EntitySerializer.h | 109 +----- src/engine/scene/Scene.cpp | 137 ++++++- src/engine/scene/Scene.h | 12 + .../scene/components/AudioComponent.cpp | 25 +- src/engine/scene/components/AudioComponent.h | 6 +- .../scene/components/AudioVolumeComponent.cpp | 15 +- .../scene/components/AudioVolumeComponent.h | 3 +- .../scene/components/ComponentSerializer.cpp | 284 ++++++++++++++ .../scene/components/ComponentSerializer.h | 276 ++----------- src/engine/scene/components/Components.h | 3 +- .../scene/components/LightComponent.cpp | 5 +- src/engine/scene/components/LightComponent.h | 2 +- src/engine/scene/components/MeshComponent.cpp | 2 +- src/engine/scene/components/MeshComponent.h | 2 +- .../scene/components/PlayerComponent.cpp | 11 +- src/engine/scene/components/PlayerComponent.h | 2 + .../scene/components/RigidBodyComponent.cpp | 12 +- .../scene/components/RigidBodyComponent.h | 10 +- src/engine/scene/components/TextComponent.cpp | 32 ++ src/engine/scene/components/TextComponent.h | 61 +++ src/engine/volume/AABB.cpp | 19 +- src/engine/volume/Ray.cpp | 32 +- src/engine/volume/Ray.h | 17 +- src/engine/volume/Rectangle.h | 20 + src/tests/App.cpp | 3 +- 139 files changed, 3389 insertions(+), 1393 deletions(-) create mode 100644 data/editor/icons/arrowLeft.png create mode 100644 data/editor/icons/arrowLeft.svg create mode 100644 data/editor/icons/arrowLeft_light.png create mode 100644 data/editor/icons/arrowRight.png create mode 100644 data/editor/icons/arrowRight.svg create mode 100644 data/editor/icons/arrowRight_light.png create mode 100644 data/editor/icons/arrowUp.png create mode 100644 data/editor/icons/arrowUp.svg create mode 100644 data/editor/icons/arrowUp_light.png create mode 100644 data/editor/icons/audio_light.png create mode 100644 data/editor/icons/camera_light.png create mode 100644 data/editor/icons/document_light.png create mode 100644 data/editor/icons/folder_light.png create mode 100644 data/editor/icons/font.png create mode 100644 data/editor/icons/font.svg create mode 100644 data/editor/icons/font_light.png create mode 100644 data/editor/icons/image_light.png create mode 100644 data/editor/icons/mesh_light.png create mode 100644 data/editor/icons/play_light.png create mode 100644 data/editor/icons/scene_light.png create mode 100644 data/editor/icons/settings.png create mode 100644 data/editor/icons/settings.svg create mode 100644 data/editor/icons/settings_light.png create mode 100644 data/editor/icons/stop_light.png create mode 100644 data/editor/icons/terrain_light.png create mode 100644 libs/ImguiExtension/UiElements.cpp create mode 100644 libs/ImguiExtension/UiElements.h create mode 100644 libs/ImguiExtension/panels/MaterialsPanel.cpp create mode 100644 libs/ImguiExtension/panels/MaterialsPanel.h create mode 100644 libs/ImguiExtension/panels/SSGIPanel.cpp create mode 100644 libs/ImguiExtension/panels/SSGIPanel.h create mode 100644 libs/ImguiExtension/panels/SSSPanel.cpp create mode 100644 libs/ImguiExtension/panels/SSSPanel.h create mode 100644 src/editor/tools/ResourcePayloadHelper.h create mode 100644 src/editor/ui/panels/components/AudioComponentPanel.cpp create mode 100644 src/editor/ui/panels/components/AudioComponentPanel.h create mode 100644 src/editor/ui/panels/components/TextComponentPanel.cpp create mode 100644 src/editor/ui/panels/components/TextComponentPanel.h create mode 100644 src/engine/audio/AudioSerializer.cpp create mode 100644 src/engine/lighting/LightingSerializer.cpp create mode 100644 src/engine/scene/EntitySerializer.cpp create mode 100644 src/engine/scene/components/ComponentSerializer.cpp create mode 100644 src/engine/scene/components/TextComponent.cpp create mode 100644 src/engine/scene/components/TextComponent.h create mode 100644 src/engine/volume/Rectangle.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a062dd894..7771d06cf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -152,7 +152,7 @@ jobs: !**/CMakeFiles linux-build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest name: Build on Linux # Run both builds in parallel and don't cancel if one fails strategy: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c43a21f97..b3b6fff5a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: !**/CMakeFiles linux-build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest name: Build on Linux # Run both builds in parallel and don't cancel if one fails strategy: diff --git a/data/editor/icons/arrowLeft.png b/data/editor/icons/arrowLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..debc2bcde9065c12edf3d47089146fbbee0f04f1 GIT binary patch literal 1343 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xb)7d$|)7e>}peR2rGbfdS zL1SX=L|+Y;K#4Ya@rfQr+$SIDO<`O5qI-ditt8a(ao)!xRpDR(?HR-8XB z_N7hSjj%u?f+f8^+>o4cUH+mL+!U|Ez?`w&5SaS zRv%)_zS_Y(pY!|fpambAFP&A|ujMBe5j>O8TuM?r?Ca~3TxQxgqDpfgCO&+(4lokE`teZ7Gn7XN$jJ!kKc3kilREjhlP-}nC>Gq3fBxu5^8SHAz>uI-lkj*?UV zzS{KEbs7t=TL4*5Zwrmy;PH^Opz{rxG=zB4zA7Okx-(@+6M3R{x5y9>jA5L~c#`DCC7 zXMsm#F);qFgD|6$#_S59AbW|YuPgftCP5KKZ7#jZKp|#3PZ!6Kid%2*Z1kE|C~@rJ zbNwz4r58$mO_LvJxXyLDaO;8N)mxS#7OYklS3X=->3gK+x68s);K*Hp_hH_rqwTJj zGQIj-_uap+Vl~hLN0R`|>O%gX$+uO0 zuVVoTcy4+g1rm6zvU?pHNWg2;<0_EAX_edSI6wj(o1V@B34B(Wy^aeg5UCPb9{a-l z-^s{Hrr~XMkLS-(i!99r2^3Be4Q~eu)Oc)q^UL7>5Am7DC#o~-t^4eMPfcgJ8omMj%5DD`WrWKQroP7#?$;6#jJSf-|p=ai&dq;yKCRc(2%> zx!0STIXW0R7!;TqI2e?u#Ms||z-`h|A3=*xEDN5;Gs#cV*=_fw`v4?H1TB0wy}ETl zpDEq*T8!i`*Mx~FcS{$>vz(ipvr7x8%5{?N^_Dup9X^}hb^%p|dzQuU|8jj`b#mQm z$Ne04RBjgQz7TJOBpIcG=}!)nE{JD&H(6#E*OxA!wUbr+W0@&Qtw6m$uK!`lZL~V3 SbS6j(WR$0?pUXO@geCyj^Bmy- literal 0 HcmV?d00001 diff --git a/data/editor/icons/arrowLeft.svg b/data/editor/icons/arrowLeft.svg new file mode 100644 index 000000000..bf1d5b56f --- /dev/null +++ b/data/editor/icons/arrowLeft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/editor/icons/arrowLeft_light.png b/data/editor/icons/arrowLeft_light.png new file mode 100644 index 0000000000000000000000000000000000000000..6f6bf9dc4a09fde3ce49f3e384e22b1d9a6f0890 GIT binary patch literal 1327 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgO_TFI$C=ao(P!xW|30g(k|Bx0k^o;TFr?0#p)|HNkrWJ;MNBZ zjwf9`cr-GcJ={^?4?|&DK~Tv=q5soGqK;G)-03g>E?54ZVPRzIs++>hA!m&)b?%sP zdVN=8IERFr{UHvoGgl_hJgQ@J{JL#T&ED=$I`50?`y7`h7#&a-IzA&W;)wK-&9kSS ztDJ9f3Ju{!03+R|Ddc?w+uO;3pz z>1of|Zn65RRrdYK`wb#4gc-#ww`^mXHo=8UDdkF_qo~-TEm=!ucwDZ#b6CFYk7m)y zC#xDJCNr9@m>^^SL;laZYdU`ZJ3S9tT=<=ITmG>_f_R2$&hOthwneKq{FmHu$?9K} z@QmLodoNy8azAjvtf27j#V588ir6J;FR3~gso482j9|HU{>AK12Ttf3Yi( zP=vF}t16n}5`t5w2>Kr-8QJ(^;m|L^JEm3r2i zXS(YvXJ;MHOpN<;x;ie`+7Kjg{Hp8nHjwcfc0G6kQfj+)+Z?%T{q>*QAIINge%>Bg z|5^WiVCgw`hDYp2_&ekU>=o)9{xtkx{KyOuIHF%Cu>XhSpGWK;yXEn4u;~RE2GsoU z`nkW$Kgx%ehyB=UAAY`$hq2gpO`LQ?=_C7fdA}u7T)(eY|9>d|%bp*J>pnkXXI7{) z0J;VXrTrtS_T$8ZXBdBk++7>@>qf$}|BMoh5;hEO3~l@kf(;Lu6Ic>{FaU+@kcA$Y z=lo5tpXYydZ;htLYpcSh(%4mTTh3i?vIpX3!4HBBf($(j2`miCK&=NjfLaZhC>3g$ ze`~FocJBdT5YPNCR1uYHX?sSx@$jpr$%pp{TCCHvG6iyW%d&g*VEO{Wt~$(695v{5!(O& literal 0 HcmV?d00001 diff --git a/data/editor/icons/arrowRight.png b/data/editor/icons/arrowRight.png new file mode 100644 index 0000000000000000000000000000000000000000..c2ac14526c8b43440f1641efe2ce9a5d0c4d7506 GIT binary patch literal 1367 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xb)7d$|)7e>}peR2rGbfdS zL1SX=L|+Y;K#4Ya@rfQr+$SIDO<`O5qI-ditt8a(ao)!xRpDR(?HR-8XB z_N7hSjj%u?f+f8^+>o4cUH+mL+!U|Ez?`w&5SaS zRv%)_zS_Y(pY!|fpambAFP&A|ujMBe5j>O8TuM?r?Ca~3TxQxgqDpfgCO&+(4lokE`teZ7Gn7XN$jJ!kKc3kilREjhlP-}nC>Gq3fBxu5^8SHAz>uI-lkj*?UV zzS{KEbs7t=TL4*5Zwrmy;PH^Opz{rxG=zB4zA7Okx-(@+6M3R{x5y9>jA5L~c#`DCC7 zXMsm#F);qFgD|6$#_S59AbW|YuPgftCP5KKp7knf!VC<|VV*9IAr-gY-r4UpwNU2x z$M=&LJ}FQUP%jj^)6%g(nxpBNnEv9`8E@2N)?7Sz;OI$#9HBc$6py%aNHza3xgz8e zvGV!(?_YN{7N7fUJ>UGf_3bTfRB{O>mD z^dB#l1qXjzpB(e#y9+~F#h+}?bDz_d7>q6cT~$fnDJR$+MeFaI1aD)z66%`#26%Fn!px>fSAekJ)?V4eHPtJ@ \ No newline at end of file diff --git a/data/editor/icons/arrowRight_light.png b/data/editor/icons/arrowRight_light.png new file mode 100644 index 0000000000000000000000000000000000000000..db2b064b5f8eec8dc354beea3838cc953dc56b4e GIT binary patch literal 1324 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgO_TFI$C=ao(P!xW|30g(k|Bx0k^o;TFr?0#p)|HNkrWJ;MNBZ zjwf9`cr-GcJ={^?4?|&DK~Tv=q5soGqK;G)-03g>E?54ZVPRzIs++>hA!m&)b?%sP zdVN=8IERFr{UHvoGgl_hJgQ@J{JL#T&ED=$I`50?`y7`h7#&a-IzA&W;)wK-&9kSS ztDJ9f3Ju{!03+R|Ddc?w+uO;3pz z>1of|Zn65RRrdYK`wb#4gc-#ww`^mXHo=8UDdkF_qo~-TEm=!ucwDZ#b6CFYk7m)y zC#xDJCNr9@m>^^SL;laZYdU`ZJ3S9tT=<=ITmG>_f_R2$&hOthwneKq{FmHu$?9K} z@QmLodoNy8azAjvtf27j#V588ir6J;FR3~gso482j9|HU{>AK12Ttf3Yi( zP=vF5i(^Q|t+#jVSD$5+ zIsS3GeXL=O=FF1i(PpZe{tlX(j~@P@(KD~*p^=nEcW0iV=4_rxPp27a#WIVjKH2sB z{P(Y`?$^{k|8ws5Wy`te)&nh}2uwS_^p$7nF>l7rF>Ai9`unO?u)$IUZ~#UWPRrKVO}F{|Hcuckb%A|A~Lx7_ObT`RZ!?F_6%le{vuH z*~_>wgk4D6`nw;fF~V{As^#n=`vn_BixyRt8~_P1N?$$vA0(t&w&vE)^B|#1KisTharK5K^Z7=fP+#YixBs) z7cuL@wwoH#NcI)^_EKNvqUKVm<^4-pX9|HJXmBleHo z@<;UR@Nlr{1sMj^++La2DgVqW?5?o=8M#K;kB4KVuQ~%s^;PbV+IRYe`XA*l&JEiQ zVu$M=;V+)MHV&jde6N`OnbLh#%5^icS668R$=y}@bu(T&eRcd}aQ$eOKaiaIYU2-3 d8Abs}=v(hybJxIgO8h#IDW0x=F6*2UngGUP|2qHx literal 0 HcmV?d00001 diff --git a/data/editor/icons/arrowUp.png b/data/editor/icons/arrowUp.png new file mode 100644 index 0000000000000000000000000000000000000000..293c9e56d5c9304119ce5ad69c350cd868f21a16 GIT binary patch literal 1541 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xb)7d$|)7e>}peR2rGbfdS zL1SX=L|+Y;K#4Ya@rfQr+$SIDO<`O5qI-ditt8a(ao)!xRpDR(?HR-8XB z_N7hSjj%u?f+f8^+>o4cUH+mL+!U|Ez?`w&5SaS zRv%)_zS_Y(pY!|fpambAFP&A|ujMBe5j>O8TuM?r?Ca~3TxQxgqDpfgCO&+(4lokE`teZ7Gn7XN$jJ!kKc3kilREjhlP-}nC>Gq3fBxu5^8SHAz>uI-lkj*?UV zzS{KEbs7t=TL4*5Zwrmy;PH^Opz{rxG=zB4zA7Okx-(@+6M3R{x5y9>jA5L~c#`DCC7 zXMsm#F);qFgD|6$#_S59AbW|YuPgftCP5J~)y|X(j1Q>M> z^Q##YCq#d+x)kBPLHLN$6Q`U#GO7y}I0>xrNz6NT%-Nyi*X<)lhHYnNnf{!=+}pU_ z^mp~ic@9EA(@6#mks(+8Lf=|&zOekKANqCgDme?m7v}%=yD-e^i)UHz^M@R8`eUYqKhx#^UshSJUGZkV=eKJDjcZlff$lt3-}zDhbkVL=>JihP zP6s-f7mt$@{(wD1d&*$)^JDwJalDy7_v8x>8({`HbxWgvTiqU*oSb}cKc~!m+vN-< z`+xXNdc)V%@bP@1b-|n`4>#$=vo-_@8vL_VO8CQMV8e9G-kqfnu04IjA= zeBhAyFC%DB$0#Ak_;|iD(=mO9!^#PNid_!;U`?oC>8bDIkl4?_(+@OKkn!>Js?e)p zp|xv1?_ITO_vAg-ns?Q)Pu{crcIekfP1k>}T(vEBV^7ZklgImCGX4NsH2X*WTw0xq9zr|M$C}xeoxn \ No newline at end of file diff --git a/data/editor/icons/arrowUp_light.png b/data/editor/icons/arrowUp_light.png new file mode 100644 index 0000000000000000000000000000000000000000..55d18e313f869f0b662fdb92cea80b78d9407e61 GIT binary patch literal 1467 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgO_TFI$C=ao(P!xW|30g(k|Bx0k^o;TFr?0#p)|HNkrWJ;MNBZ zjwf9`cr-GcJ={^?4?|&DK~Tv=q5soGqK;G)-03g>E?54ZVPRzIs++>hA!m&)b?%sP zdVN=8IERFr{UHvoGgl_hJgQ@J{JL#T&ED=$I`50?`y7`h7#&a-IzA&W;)wK-&9kSS ztDJ9f3Ju{!03+R|Ddc?w+uO;3pz z>1of|Zn65RRrdYK`wb#4gc-#ww`^mXHo=8UDdkF_qo~-TEm=!ucwDZ#b6CFYk7m)y zC#xDJCNr9@m>^^SL;laZYdU`ZJ3S9tT=<=ITmG>_f_R2$&hOthwneKq{FmHu$?9K} z@QmLodoNy8azAjvtf27j#V588ir6J;FR3~gso482j9|HU{>AK12Ttf3Yi( zP=vF8gZpO5{2UE;#f))&u`@Z*OoLtFnqG9Js@)d?Q>Aq&N> z4lD+Bf&z>^@hk^`ZtM9U_58Wtyr1ifD|a~?{L`NIS>?T1#k<=o`G15P?mgZ=|4B}B z`h3-$+DsR!|J*cS5M+E|`)@6?0@H9s;~)&)w0t0_?jn^lmll<3%-lb0pUw*!S zvW@qrn_D>?LRsQee&+o-t^C32WV1?qOglr!r$b*qIJe}#>(d4nXDkc?3=W`jjVcUM gO(H$dpy;?U_x`Gq9i}CU7hFN&p00i_>zopr0MUyhtpET3 literal 0 HcmV?d00001 diff --git a/data/editor/icons/audio_light.png b/data/editor/icons/audio_light.png new file mode 100644 index 0000000000000000000000000000000000000000..6c62da09faaad066352112ba4df58615b04c767d GIT binary patch literal 3859 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 Kc%dor*8c#In;vce literal 0 HcmV?d00001 diff --git a/data/editor/icons/camera_light.png b/data/editor/icons/camera_light.png new file mode 100644 index 0000000000000000000000000000000000000000..23991d6d6d16c35107a78a51301efa93a04f17dd GIT binary patch literal 4235 zcmb7HXH?V8v)&|<&{4{ZG?Csy5u}5FfC>r%3ItFIy?1HS0^x#Ai)JNLN}=1T;Zz-h02?Z}_GjpDqxn*a2mWNZ669B+tbcrWRm@wpGC;vMdn%9Vx849`V; zvU|MtR64u-wq?EDH0Mi$!YjYoY((!13ZHF%L!)(hjTahxvmZTULSnb>9ewb32imA>!ZiYHSvgh{QsD0@48?pD``A#F-ydJ9_p$r_=|o$jfI+rflFe$Z%+BV8dPvLs zjE?a|ap}L#Uq#2BP!Ut!;*PMe*X|aMR8uJ?>Q#>!1W_Nn*T4)qX-}kc?p}MSfnW!rk+NX%lkAP4*<28{=r(i_KQ;dV>s4vgEue zR@h%Vn8BEAbW^|`h%^lw{Bad1WXQG+4N0Gc26e(CC_@Xzv`Ey^q+vPpNLdq`CzwOC z11m+}p--wB^-DYBvt3^ix#hL_VK}@w_Nm3x>aop@)s1zk*7~1h;Bz+eNv2AX6w570 z&+}(3z`?(xtgYe!3*iVq=Mu&8;KaWRc3QD0js>EwTiIBmR^a>`$A!#_4($WLd(+Co z>~h@nIwgM4$r0AIwJF9f{f|W;#|jqxB2R9Lh74>LSh_oXw-WQf#pUuip*iT5Z?%&X zUjJE>EFo|4@*}s<;~wV%Fu1Sviq`T)Xyve!;`#Ab`n$KA&ATOxL=!E);`YB8Z+Fq} zmuG*>B=DZQFKhLmOh>YaX%xO-WE~l3p#T@-0Xp!~H-?0JiQ;in?FTpF+4MR?pPZo| z!M?=5vvlDT_uRF6oOBfP6&u!21KVjVB4nR|^@uMNp?gxVuB%l&kib<)%t)x+N%e>k z8*6Wbj8M@PJ&CldF_MclCiI*5_=cVqB|90Em()Op$NBo>0PumZ~ddu)JPeI-!&)k^X`HQ2XZ+=!b z`c?Z#&{eTLXT+E?sb|+gNdBFm$)$qH+K_yGwgHvAZ#X?O&kw|FwN>3{3J(qlL%63{$DU+|*TYL;kOcnd_$%G>n!z+d9t#M?GD}cD)n$D52=QI889$ z+L|5jR)L1HfBlu69qlC<@N1GzyPQ`N`S|BNPIYZUHIb)jD$xInFYS(#IhWhv@#5a> zgt<59<*8EL&*fuo`7X`cb3lFB^+GmXG<5tX3H|yD*EF^^yJ5%U@KTK zZoLCe^MaJCJsVz1qg8g&9nB5|Q8qun1>7c1|Ao-fY)RiodNc<{_kBMKr^=VcKWt+k zXun>Otgk(Ny(NX;yT!|F=fEfwChG%%Ty9{6#K|H|@57FN2D#qgoFk9#?P;AVqK^t#I2)Jo4X@CJ$>Qcq>nD|Xz(WFX89;)~^=)1>Y* zKRE8Z>eW2T4IDRhLd8AgmR>=}OzT4RHgYkYZC|N6AO*G|gPx2xQHu!#1i?)?^dtRy z7F=fM(>Ru=BVNXaPMI`uw`MrJ#vfnUgTT;!tgEGq4eu!p|-4rdIKeiP}^DiO;yFo$qUtw%WZRi*8@wLb(d!twpz*CLV zkh}I>cNpsr??={lE+e^d$T6`(Wbr5zt#M>e=#&-~oGeU+Bp`byHUXT;{izKi5~t&= zPHOluUvqmROQttX* zPtmqM?PPU3AUrTm4Rhg5B`IlD@hGGe%J|Q(8Us|dMjtZrWBf?oe?QHtbdnY zHY(WaaP0-6k8a^#Wc<8DBByRR5(pQAG8*QfHuXt`tH zkBeEUZob9|B}Cb9KbofXD76mZ4j(Uwt6jxinxnpYB_ACG{2SJJ#f$f}u1TSZ`*_aB zyFyUJR@nKk^te_TMA?0Lxp%u^$W!KVLA8OQvP@rUahw{otHh17p1!}$l~`;WXHZ*J zD}@frAGUv0%L<6@LuDzXdFr7=b^MJz-f(l$JfEzQlp>uoncsO|MN)z7P+O%Y{F zo$>}7)sVi(M32apcw|)+!Zh#e-4PCQ@bWh+TE0bBjH3^tjKA^Fsp6H%g0Zq>2jlva z$buufKp87M%v3X$EOf;Gj@KJZRw$iuo`;_o1U5t9*{T7u}QF(*jE~g z$RzJ`Hm=|6%yZN{Cqvba`9XEN};xPO~u&XqiwJ=?N| znGvqFWTfeoLo{hW=<->HkKj8bPI-tUr_x@c+(5VvaN~yiWcrfh7%+U0oeAEm0q3Vh zPiOr|k^J34;uR`9lIPpQ%_uCAxtDuywG~qub z7_Qa9$=*)OEc-u0uItpBXGH%a6Yl%eOE-ZH3od__9#);365g}Vi2m+1+%|fW3lm7x z0#yqI963qeq=c`6^02est`jUK=*fCcW*49_o|DsR46|VGis*ZF_d}l@VR2Z95`8(n zYm*b|p9rL7UdrGLsbQ3oeh@Ja->S zQNYRv%+`{xw8HsvlO^!~1=X$jB269J9_{_v)g~i$0m_M1%qPv1cmH@XPxwJhNe8ei zuwly=r0e6^#cp1+)xUFd2dIHRrY8)y(a73{1XfTJk2O6n8wiBIo+^Elol>i}GErBM^yZ&hk?c+Ub|=fdA(J&SQl2r5fq8dkq% z|13>Ix88&0RtvNDcu>wpH!?)|&yTgi#qZon)?oH}U@Qw7x)cF=1E8n_m3rZgO+Rq5$!9C3?A~llYks|Z*@GuY9J~5TF?TV0P35on z>{>Ok1-bl#KU}IF496 zNUkZHw|0A)Te9js>1^rc!`^@(kpGdfm;;aX+m5BOiGvHEq)~+Bsy@nzLua~F(v;P* zDB|z8cBRqI$#s3cfRNC4%lXhILSEq5b0zYFbfJXUThu+RkQa$f(sm^!pdZ$sMQ!Th zpFJt=GPr!e)g;l>4r686nVyYI&(wOaK|PX8XM%vv>Ke6U0Am$*B_8P!F~kUuvSl&e zHn+#>nr>KWn}mi@zEnD!DFf_#!Z1QTuB3}O=h+!_RZv272 z-u!RXQ`J&@4K)kX>y*DFpY-Ml0(&*hYlGtgF|5)|Zpun&Oce?wpAVb?;nal61l4{e zutdJ*Ky=N$1dKcrtSEsoxA8i1OuR76JVdv$rv#a?c%2P|SFg)ECZNl?ajPtJ+Dpj? z^g?e|%4;8S^md;BK<(B3bG2W--FRK0a5xb*A9A*GBoxKlXACj~8sBXAC0I)|^6wqL zMZ~Gkg+TuWSv6LSDpfo`#stQk27h;@&O#Wnw@|DGU5e}+I3h?} zwM@d~q+Q(5Ak|B#nD(z5@Cta(8hbucWdQE3#87Uklr|eg?VZ#(a&%tkNno(NhES6z#%)J(9)n5cpazp#axGZ4al%N(W!`UCDm}wQn16 zAd|TlK0(E&GICr>@q2lu8f~=bpjN%s+5&24ad*h&qd(8EA&8fz^9k%Y!inB1k(O|>i!%x1x(qt9XeRMDOj%}74HP=nW zgwS``Wz*&ocGPW}S8w`Ai&4VL&bVje6_mU%h;@0czSE-7%4|Or<;y`f+jK7dlQG^JRtz`2&9`! zK$2i))Tg{O4DjY8)@apyB=KAsOJDbJd9$0Y`(@o4sRg!r^byOTJJy*$-x(P}Z-08g zddIqOI&s`QpYR5DRcGL>aC?4SCwJ0TEJlkL7K$d5J0*_d)UIUG+V}a!3ZlYYtK;h) zFaUXM|LC6>U6+JjNU48;m`MJU6%t<0b+D4lriGsBc2ez?J%63J;;5)}B# z(Ws{wlMml){$xW)wkQkdKoVl-J>rXZ;=EErn%=B1>FN|rBRl1eAPlZ0aNHne!<@MS zNtD0sA-Q@qNmm6zd%|ftI!e&Rt+(O={Yt~VKU*l$ibNIuoW|K|*+bEatRq}3E_w;O z-pV0y`DLUj?{p%t3}`%5SO|J+8B5PehwLC_9NDDX4_eqm%2qFdeIPDeKovY92YG?j z=7aO(_?*aEO?-+)?8WIK&^rpj`a2LirsZgbwF>Y7gR+yCi|RFL#r!j!^ABzR7zv{` z>u@Tomf!L<`9{ePx^ElMdgFJ5*sabg-b37D z`}QOj^r%`hR-c)pla;^pR9%T@hSHY7%KYo=d^EDxjB$*vuz;z;=6#Cl%s^%m8;;kG z_&@{q<-UIRUzGT}DCz)@Niv%vkC2pDvfthHS0UFDQZisY2p8Y3_mhTos@jAq6dW|Y zloHml{F+dl%nPA!|EA1E42TeWi_W`{9Xqm_s{6{#`Iq?XMHl23!s6)$yy>by(@M+D zBXfDE1h$jxqaV|1#)LZW31|hq5gcDjw&*}F>nQG(^B0E4FoYb)B^B1(6rN-i@VBOG ze8h-;m5)Ec)-;SY=N?J(8$H3Tan3=f%HvOA{nPq_iV1||LMF4?(Qu}DWtd8EO|Q;jue#IqDt#xW zyhL5Oi$i|UveaP7zW(bids^Oo{t!*7-g5)(aIBCWV<7o#gK?;@DWi7Y%eXr+a?zkU zlRv|k7qsgvmZmULYEtT!!Z4vzS04p71mi+xn9K?bq0(7jP1JC4l~ap&`9p2Lg#9Hp hzN7n_CbkxUeK}xkawF<`=-q%digZW0F`5+N}VDkwHA0W=T^OF#P;^at;qJLfrP&U@$HIWunt)t|IR z_iJ4Mz#6g-VK)E>7!d%y5)Ps9We4Eo>|rl2D%s0x6FWZkz~Lh-0Dj2kWaE7vx|_B! z_V9oF%454v+#_ni_;vG({PV;rs#N#3b6)7YmfzQx*I`I4HA6!k5#pA%;2`ZFkF^UR zE{VN6A=_{{cirCBV{BR0(YbGqDnW5DOCT{?5%6?dPey%SXXW|RL1<%dw+HAQUA??N z+psPjefZ>L(S;eT^4C)rJ^0LRkdtM%9aioBWU^D;&D&@5@eI{xv9fSbJ!VPAI1)J<)+F!>7WPiY{x zI6c=yyfzzlXJQW7AJJ&~YeTHK%Ny!@oR{_*czZHkc@q!oE)2+klRfxr2rNw zzBp}j{k&q;e zrO!9?H>BvZviMBN#t{7Io|Bh{M+d{PA5d(9%14#x>zZ+4ClXsNSO4LlV_Tx!zIDKO zf0r+JLTol;tTdlEGW-5xjBkr~Q;XHzPDJ}vP)nPmk%Hf?`MZ6Si9)yNfhD570L0wx zu4;wp!*2?|qWdr;oHpvtLIc><1?3q*cXD1?K=lZx zo*tZNjL0pQ$roao1I=%~T?&iYUC}drqNeV|z4_=wTH+Oe=Att>3pI@;66NDt&7u% zVv~GpP${*^6LMg%>4%{?wgkBBYi3py7Q7+$Ss^vbp;DR7%{dCrr86{#|h$}Cw?U=e# zC`iI7Mi>b|FdK*o%92K@(+9>z+XsnDH$ohSOgN4YS|7~@IR?2!&W?9c zyUUk)$88p!OmA$Y1TbDg^D)&$hNe>`>|5$M=Avk+faNs(DhG>Vl;ar91~X3;#?kw= z?u9Sjk;Fc7rHcN-GWw;LZDjE0^YOdyJ=CQ8jWC`TzByT}H7M5pe=Y@gxsG$T*4p%4 S&pD3!tRWNq2^F5qGyei(Q3Egl literal 0 HcmV?d00001 diff --git a/data/editor/icons/font.png b/data/editor/icons/font.png new file mode 100644 index 0000000000000000000000000000000000000000..886fdebdcf952c8e2e573e30b614cc53cde74baf GIT binary patch literal 1125 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xn)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgO@6aww4J5o(Pz$)6vDD7uBj1!m-t9HJ{q_I>*UvDo2k77U)|1 z*cee-PDz;Hh=KJHjdyn6>c33fWMp|hP z(!-7(&}R9f*dobu@{G|l70t$Nk6+8#?Yrk%8TPxlzE93H+wg$8(0QZ7TRP1RCKj#ljz4v8$ z_JJ?zFRv~Wvt+8;-q`(m*|Il`)2$u;Pgb10N&9Ce*G`!ab#C{kFgk5__&)1;bh7+% zeuu7IUUwMS+Yflm+jRZ*p53drZm&OY^6NL_nzEvO8-hI70po=&$=lt9;Xep2*t>i( zP=vF=9d|AJ#D5m|Qb!Iy0k&_L)j!Mh*rAh6bSh4CFFCEP3mCe((H()!$X7AN*WDspfY| zqC@%md0VTwf3PyJFbFU>fRZg515@mQjG1tOOZj@0aPOX-p;wET(Y3%0!PJXWL}K&I z2IW;*{z1Xd%fEXS$kKly*( emS-RqR2 \ No newline at end of file diff --git a/data/editor/icons/font_light.png b/data/editor/icons/font_light.png new file mode 100644 index 0000000000000000000000000000000000000000..e81a4cd481f13d8d7f5664c29a845499c0071556 GIT binary patch literal 1103 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgO_TFI$C=ao(P!xW|30g(k|Bx0k^o;TFr?0#p)|HNkrWJ;MNBZ zjwf9`cr-GcJ={^?4?|&DK~Tv=q5soGqK;G)-03g>E?54ZVPRzIs++>hA!m&)b?%sP zdVN=8IERFr{UHvoGgl_hJgQ@J{JL#T&ED=$I`50?`y7`h7#&a-IzA&W;)wK-&9kSS ztDJ9f3Ju{!03+R|Ddc?w+uO;3pz z>1of|Zn65RRrdYK`wb#4gc-#ww`^mXHo=8UDdkF_qo~-TEm=!ucwDZ#b6CFYk7m)y zC#xDJCNr9@m>^^SL;laZYdU`ZJ3S9tT=<=ITmG>_f_R2$&hOthwneKq{FmHu$?9K} z@QmLodoNy8azAjvtf27j#V588ir6J;FR3~gso482j9|HU{>AK12Ttf3Yi( zP=vF(4|B^+fKI&38_?2?32D4oKWAxllP>h5z%Vg)wxDP$q&CjeB1x+UmasZIHkTs z4Q*G@5Th8o@&lE=YA?*xE5+jePfppq|1T)>69pRL>@$o{#+!zDwS&}q My85}Sb4q9e0NtB7zyJUM literal 0 HcmV?d00001 diff --git a/data/editor/icons/image_light.png b/data/editor/icons/image_light.png new file mode 100644 index 0000000000000000000000000000000000000000..8a8128b4cf81060de73c60b247d642cdb6d38f93 GIT binary patch literal 2449 zcmb_dXH-+!7Cx6CK|nyIs0i^fqeu}cBQ=rUM2d=n(t_AQ2%!WBkTB}FVBTQIVCaql z36hA?ClDzU3rNubq6S8g5==q|4N2a`x8C1b>#a5SuDkBp``qu_`|R(WeY5<1JvJz9 zQvv{N@bYv$3;+ZVA)u%Lze4ckXJF&WZZ{qgk?Q~2$UQ@O?$T)5zuAPmsL-B#mLSKRNUh3(5iT#^X;v? z4ihwI0F#!+`gP7k6i>==dVsZH8*JiGVuIRr+MiH!U#Aq=w7?;dhGXn~V=_sH9ax{EIOf`9>tPul)LUhZWSSu;0Yjufgk? zO^^8Gd(@~SR{Pw(Xt8*(HrRkH+gjgeCq1#5BL0!p+pF)v?yjNp-5())J0 z;D2=RDG8G2bx%^~*w6QvF~^tsd7LT4^#@hCvE)lG*izSb(|RJ|v`{c?DUfD>(;oPe ztH#uS!Llfv^!9KCvbDRIRdx&ZtjBpCkB1c;SbHGn-PB~*iA?bFaYIhZZCbzMrwwx& z_hC1)V4-i@1b+JhaJntdA3rDXqhLroj~j1aO%4I0Ml@wqfYq41*k4K2_; zc(57r>v!WaLRLGUm+&8NoMxYcm`5@G=|hDPXZ9bFV4u1PP|+Ad;Ba*yrJ{{Xzv$q2 zN@2znNp}zg01AD87Dd1N0a|5y+d&zhbO~)})2hps@` zRQR;4zcw?|W#l~hg1?cN`|RTChUB6Fkn0V|0LX(K-;X`3D)- zxR|B{;dT|iNf}wwHXyysuIXF)BC@Pq7req1r`9(X+fC@UxC15_rte>@YRVMZ?~x;a zwa4G%k=oIYz)Z%IWr!A*S<|lp#8x57rYJ~7pw%btUND6q_Q6zHm}FTI>>3hxA>=_> z9PhRH=SA77ioL{^dug+5UKu2PF61|D4wwBEx+=R$7hk+|+P>!J(b}wq@wx$GLMe;H z2xAy_B4UDj!!&`i3fYDGm{7UA7}|U*CRNge;pMZ@*j%Vsu5y@|9+ts--C|EDogUuP zjZ`Fe*nFJ$JRnHmS5%Sq3XRaHV!6&nxikt7J%=87p8cJ-6Uehqq|TSmlUYK^5h{`b zF>`t7PiU+rYZ%Q|!OpT|3GP&6C2g6CG!rW^sl2p=Pt!$j;5kQ7$+C`3)niui9Im90 zw}L297d0PQ`2_wH4!$hy9fzicl5xn09c{!mN}cb`VvzXrz!E^rUfv&7DeQ+tS^1i6 zHAEdSSKCtZKcG(+!hPyHaLkBpNBj=V{th%d7|WYZVVAQ=zpq7XTNr2MBmezjt-9<_ zL>X-Yy1$$;4>YlZO1KG7*VdGWJXo;|Cy7w>Snh0JJxnc2p5*IbGV?5a!S}2*m2cu1X0`4LS>L)b{Q4~I~ zCLAtzH4nxV*VOg+rui;n6Gwf?kqTz-;cZ3OJFsVE(LUs3td26-QXugt_~nItUp;BK z7;IT7nAaT)xXxaw~e5_T)Pe(G1!ZF@2e?sRi^?^LT8(4uJCouC#IjyV<1 zRCn0=*C*7JYBB#Nznkl}gtSbmY)m?E_k?aUcsNN8+}*$;k!;nB>Z^mp49PKAbG4(V3MQZmody8{_Hwah~; z>SK5KxqQNss%7#7YVH%t7VuOv_kQV=$7bvC8U!S6#b5y=PFuz zBX2sYR-$poXMD@-3SrSP5oe^$?U%naa*G?v)%^ps% PVF0|`d|j)Yu|NL@#@|ZS literal 0 HcmV?d00001 diff --git a/data/editor/icons/mesh_light.png b/data/editor/icons/mesh_light.png new file mode 100644 index 0000000000000000000000000000000000000000..5babf4762855d093c553e5253b0ad7078a55bf80 GIT binary patch literal 4345 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 F{SUxr3x@yz literal 0 HcmV?d00001 diff --git a/data/editor/icons/play_light.png b/data/editor/icons/play_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2b078e35c5c52dfce7e5e7bea13a0ab3d8314449 GIT binary patch literal 2291 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 literal 0 HcmV?d00001 diff --git a/data/editor/icons/settings.png b/data/editor/icons/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..2e749c39ed7d65786faa517f49bce867128b5679 GIT binary patch literal 7374 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 mKsicy+I5-^G5-S#O}=0N literal 0 HcmV?d00001 diff --git a/data/editor/icons/settings.svg b/data/editor/icons/settings.svg new file mode 100644 index 000000000..817c782f0 --- /dev/null +++ b/data/editor/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/editor/icons/settings_light.png b/data/editor/icons/settings_light.png new file mode 100644 index 0000000000000000000000000000000000000000..11a828e871a1505063975f4865a7250bace43fc0 GIT binary patch literal 6386 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 M%UH8k-8J@q06^FL^#A|> literal 0 HcmV?d00001 diff --git a/data/editor/icons/stop_light.png b/data/editor/icons/stop_light.png new file mode 100644 index 0000000000000000000000000000000000000000..274fc884cebf058fe597a1dc797f38e35a0c9b9d GIT binary patch literal 1004 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgO_TFI$C=ao(P!xW|30g(k|Bx0k^o;TFr?0#p)|HNkrWJ;MNBZ zjwf9`cr-GcJ={^?4?|&DK~Tv=q5soGqK;G)-03g>E?54ZVPRzIs++>hA!m&)b?%sP zdVN=8IERFr{UHvoGgl_hJgQ@J{JL#T&ED=$I`50?`y7`h7#&a-IzA&W;)wK-&9kSS ztDJ9f3Ju{!03+R|Ddc?w+uO;3pz z>1of|Zn65RRrdYK`wb#4gc-#ww`^mXHo=8UDdkF_qo~-TEm=!ucwDZ#b6CFYk7m)y zC#xDJCNr9@m>^^SL;laZYdU`ZJ3S9tT=<=ITmG>_f_R2$&hOthwneKq{FmHu$?9K} z@QmLodoNy8azAjvtf27j#V588ir6J;FR3~gso482j9|HU{>AK12Ttf3Yi( zP=vF+N+$-UbB` z*Ng4SB0G7K1iLSAIcpU66f4<$d+){s)Jh3>FDjfe;{flQS0DZ*?y}vd$@?2>=+C5N-ef literal 0 HcmV?d00001 diff --git a/data/editor/icons/terrain_light.png b/data/editor/icons/terrain_light.png new file mode 100644 index 0000000000000000000000000000000000000000..661489e8cb3718ad509a59fe6d92788fec53f0af GIT binary patch literal 2518 zcmcgt30D(m7oIq^K`l+)DIie@g32a}0>ZE(5m^K*iwHqQu%JM|5SEy@K?OxftcFEa zQH}}`Q6vRSRAf^m$|evNO9TuM5EBA~WIFNdKj=B%%$f7v`#kS`?!9x*J9EX;eZT5T z^_2hssN!85y#N4(m?%KyM`Q^kmYqbLOQ#$hJn;?=o1=+QCr?F&0)T!NCCl37m96IM zVDHlH>+SZr#58-}o-EeRC{1&E#l%xZF*hzHa0G7f*+Wn~rt8cjG%?TkHE1+c<=%XkP+;EBO1DpTw>rgJeg(@m zs$FVi3m$QIIXLgJ_R!Sc;Cj>`e&-I2?O%V2aXe(!x{+wKBk!5{gD%U6AqL09kN2Bt z;600!A8uwkuGBu!dB|@4hT8snLN+P2mQCJ9%J@%L=U69hFRpjAl=gh`;A{y_18*cH+6F_`@Jtjg!K!7V z9>+_4w$(y>734b8ZMIsxcLh7z<EO@ z>w0pOF*&6eL(sjaw=i8*SNSt3UnXUl_e6{=0_x3j&XJi$OCNu%r>Qj9l$#tMI_CjV zfUYrxQ^`%J@PvpH!%diWo6Ur18;EdmrAUdvhv{RgR`53o>Iq%8@!HurR4EbWFzQHm zC^gPl+f{68$>Z;_b6XQUv0O_E`SsrJqu-*caxBvhHyJam zp7Ef1Z>;SOHg$l)eTR#`y0m4%m+rehD79S(hHsJf@X(X^*`C!lxQ*o@hNQOkJ7Sbi zfh=Mi9Ly73Sq*6h2aM$8-NzK(sC*pPG66z+X86=B!ACRJn|`sf%$@;sqzAFzHdYZP zObBldL%>XBco0)?9yPi&nATlZU8}xD>N|kWvxfH^AyxGF)cmH)iszx5kbmhMFaU5OoQ!nPTgCl4HukJ%}~A+#pvvfS!M?|Smnj_<7(Va$&+ z!Yt7m0k5nVfq)jdnHY6zQ>c-RI~z9o>2n|jC>JQy7im#O%vTQhApUrwRwtTgFJcv8 z$aeH0L31iOzhfpxbdY#YIRmOHg&Sd7ThiXq51E{Q+VyXad2Vv zX&VzbCBILC@)m!sfow(C=;YJS+rD{?4uvn$x@5FzrOZyPbjnkxFdG{7SOnPA@up+9 zl5@%-TP!!^fMO7SzC8A0ZZB1M6zjmUj>veMF2R!YkR-tOyBe z5uk9N5Me64?hGTg@6X1m$;b4)rf`bbc>M-@&aJ+?2Fcd1H(B-z!Z3%2=Vc^TJ*NYU4DLuzEv z#&Iy`3wWjrExs+I7WBT_ojT`4vE4=C$`@%+ER390%~M#C-a;#%dvqSkf;N_dt@a=`GshUfTj|kAqwV+42TfGhB0{eGC~2T7?JpzeN?9y!*mg` zK6ke@;TG7+ev_1En_BjmLQSAkktSwVQ*8U$@XZ4rtick@FWyO$VB%YOdLRoEKDx>k z%kvXOeYzp^Lwb9sujZdVhtj>iCro!C>DJHVOg3x$r`a+DMtU z)Io>B<)M{?5KNg=5)UzIkHJh?Q6)qu)49UOsLr7rG?l~?jNelw@d+Yy{ftPa$Y_T% zw1_sl2hj#eK&C8Y8HH=QiN?pQ{Q}80Ruh#wUQ+(k&k+eWnAmo^ V7_Vg^Mn6 1.0 @@ -295,10 +295,6 @@ void main() { vec4 resolve = mix(currentValue, historyValue, factor); - if (isnan(resolve.r) || isnan(resolve.b)) { - //resolve = vec4(1.0, 0.0, 0.0, 0.0); - } - imageStore(resolveImage, pixel, vec4(resolve)); imageStore(historyLengthImage, pixel, vec4(historyLength + 1.0, 0.0, 0.0, 0.0)); diff --git a/data/shader/text.fsh b/data/shader/text.fsh index cddc19213..8629d60b0 100644 --- a/data/shader/text.fsh +++ b/data/shader/text.fsh @@ -1,44 +1,84 @@ -layout (location = 0) out vec4 colorFS; +#include + +layout (location=0) out vec4 colorFS; + +#ifdef TEXT_3D +layout (location=1) out vec2 velocityFS; +#endif layout(location=0) in vec3 texCoordVS; layout(location=1) in vec2 screenPositionVS; +#ifdef TEXT_3D +layout(location=2) in vec3 ndcCurrentVS; +layout(location=3) in vec3 ndcLastVS; +#endif layout(set = 3, binding = 0) uniform sampler2DArray glyphsTexture; -layout(set = 3, binding = 3, std140) uniform UniformBuffer { - mat4 pMatrix; +#ifdef TEXT_3D +layout(push_constant) uniform constants { + vec4 position; + vec4 right; + vec4 down; + vec4 characterColor; + vec4 outlineColor; + vec2 renderHalfSize; + float textScale; + float outlineScale; + float edgeValue; + float smoothness; +} pushConstants; +#else +layout(push_constant) uniform constants { vec4 clipArea; vec4 blendArea; - vec4 textColor; + vec4 characterColor; vec4 outlineColor; vec2 textOffset; + vec2 renderArea; float textScale; float outlineScale; float edgeValue; float smoothness; -} uniforms; +} pushConstants; +#endif void main() { colorFS = vec4(0.0); - if (screenPositionVS.x < uniforms.clipArea.x || - screenPositionVS.y < uniforms.clipArea.y || - screenPositionVS.x > uniforms.clipArea.x + uniforms.clipArea.z || - screenPositionVS.y > uniforms.clipArea.y + uniforms.clipArea.w) +#ifndef TEXT_3D + if (screenPositionVS.x < pushConstants.clipArea.x || + screenPositionVS.y < pushConstants.clipArea.y || + screenPositionVS.x > pushConstants.clipArea.x + pushConstants.clipArea.z || + screenPositionVS.y > pushConstants.clipArea.y + pushConstants.clipArea.w) discard; +#endif float intensity = textureLod(glyphsTexture, texCoordVS, 0).r; - float smoothing = uniforms.smoothness * fwidth(intensity); + float smoothing = pushConstants.smoothness * fwidth(intensity); - float outlineFactor = smoothstep(uniforms.edgeValue - smoothing, - uniforms.edgeValue + smoothing, intensity); - colorFS = mix(uniforms.outlineColor, uniforms.textColor, outlineFactor); + float outlineFactor = smoothstep(pushConstants.edgeValue - smoothing, + pushConstants.edgeValue + smoothing, intensity); + colorFS = mix(pushConstants.outlineColor, pushConstants.characterColor, outlineFactor); - float mixDistance = mix(uniforms.edgeValue, 0.0, uniforms.outlineScale); + float mixDistance = mix(pushConstants.edgeValue, 0.0, pushConstants.outlineScale); float alpha = smoothstep(mixDistance - smoothing, mixDistance + smoothing, intensity); - colorFS = uniforms.outlineScale > 0.0 ? vec4(colorFS.rgb, colorFS.a * alpha) : - vec4(uniforms.textColor.rgb, uniforms.textColor.a * alpha); + colorFS = pushConstants.outlineScale > 0.0 ? vec4(colorFS.rgb, colorFS.a * alpha) : + vec4(pushConstants.characterColor.rgb, pushConstants.characterColor.a * alpha); + +#ifdef TEXT_3D + if (colorFS.a < 0.5) + discard; + + vec2 ndcL = ndcLastVS.xy / ndcLastVS.z; + vec2 ndcC = ndcCurrentVS.xy / ndcCurrentVS.z; + + ndcL -= globalData.jitterLast; + ndcC -= globalData.jitterCurrent; + + velocityFS = (ndcL - ndcC) * 0.5; +#endif } \ No newline at end of file diff --git a/data/shader/text.vsh b/data/shader/text.vsh index 6764f3725..ea5276f3a 100644 --- a/data/shader/text.vsh +++ b/data/shader/text.vsh @@ -1,7 +1,11 @@ -layout(location=0) in vec2 vPosition; +#include layout(location=0) out vec3 texCoordVS; layout(location=1) out vec2 screenPositionVS; +#ifdef TEXT_3D +layout(location=2) out vec3 ndcCurrentVS; +layout(location=3) out vec3 ndcLastVS; +#endif struct GlyphInfo { vec2 scale; @@ -16,31 +20,78 @@ layout (set = 3, binding = 2, std430) buffer InstancesBuffer { vec4 instances[]; }; -layout(set = 3, binding = 3, std140) uniform UniformBuffer { - mat4 pMatrix; +#ifdef TEXT_3D +layout(push_constant) uniform constants { + vec4 position; + vec4 right; + vec4 down; + vec4 characterColor; + vec4 outlineColor; + vec2 renderHalfSize; + float textScale; + float outlineScale; + float edgeValue; + float smoothness; +} pushConstants; +#else +layout(push_constant) uniform constants { vec4 clipArea; vec4 blendArea; vec4 characterColor; vec4 outlineColor; vec2 textOffset; + vec2 renderArea; float textScale; float outlineScale; float edgeValue; float smoothness; -} uniforms; +} pushConstants; +#endif void main() { + + const vec3 positions[4] = vec3[4]( + vec3(1.f,1.f, 0.0f), + vec3(1.f,-1.f, 0.0f), + vec3(-1.f,1.f, 0.0f), + vec3(-1.f,-1.f, 0.0f) + ); vec3 characterInfo = instances[gl_InstanceIndex].xyz; uint glyphIndex = uint(characterInfo.z); GlyphInfo glyph = glyphs[glyphIndex]; + + vec2 vPosition = positions[gl_VertexIndex].xy; vec2 position = (vPosition + 1.0) / 2.0; - screenPositionVS = position * glyph.size * uniforms.textScale + - characterInfo.xy * uniforms.textScale + uniforms.textOffset; - gl_Position = uniforms.pMatrix * vec4(screenPositionVS, 0.0, 1.0); +#ifdef TEXT_3D + screenPositionVS = (position * glyph.size * pushConstants.textScale + characterInfo.xy); +#else + screenPositionVS = position * glyph.size * pushConstants.textScale + + characterInfo.xy * pushConstants.textScale + pushConstants.textOffset; +#endif + texCoordVS = vec3(position * glyph.scale, characterInfo.z); + +#ifdef TEXT_3D + vec3 right = normalize(pushConstants.right.xyz); + vec3 down = normalize(pushConstants.down.xyz); + + vec2 halfSize = pushConstants.renderHalfSize; + vec3 worldPosition = right * screenPositionVS.x + down * screenPositionVS.y; + worldPosition = worldPosition + pushConstants.position.xyz - right * halfSize.x - down * halfSize.y; + + gl_Position = globalData.pMatrix * globalData.vMatrix * vec4(worldPosition, 1.0); + + ndcCurrentVS = vec3(gl_Position.xy, gl_Position.w); + // For moving objects we need the last frames matrix + vec4 last = globalData.pvMatrixLast * vec4(worldPosition, 1.0); + ndcLastVS = vec3(last.xy, last.w); +#else + vec2 clipPosition = 2.0 * (screenPositionVS / pushConstants.renderArea) - 1.0; + gl_Position = vec4(clipPosition, 0.0, 1.0); +#endif } \ No newline at end of file diff --git a/data/shader/volumetric/volumetric.csh b/data/shader/volumetric/volumetric.csh index 1472340b8..5023de297 100644 --- a/data/shader/volumetric/volumetric.csh +++ b/data/shader/volumetric/volumetric.csh @@ -130,13 +130,16 @@ vec4 ComputeVolumetric(vec3 fragPos, float startDepth, vec2 texCoords) { cascadeSpace.xy = cascadeSpace.xy * 0.5 + 0.5; + float shadowValue = 1.0; +#ifdef SHADOWS #ifdef AE_TEXTURE_SHADOW_LOD // This fixes issues that can occur at cascade borders - float shadowValue = textureLod(cascadeMaps, + shadowValue = textureLod(cascadeMaps, vec4(cascadeSpace.xy, cascadeIndex, cascadeSpace.z), 0); #else - float shadowValue = texture(cascadeMaps, + shadowValue = texture(cascadeMaps, vec4(cascadeSpace.xy, cascadeIndex, cascadeSpace.z)); +#endif #endif //shadowValue = distance > uniforms.light.shadow.distance ? 1.0 : shadowValue; diff --git a/libs/ImguiExtension/Panels.h b/libs/ImguiExtension/Panels.h index 767b16097..b6536122b 100644 --- a/libs/ImguiExtension/Panels.h +++ b/libs/ImguiExtension/Panels.h @@ -3,7 +3,10 @@ #include "panels/FogPanel.h" #include "panels/PostProcessingPanel.h" #include "panels/ReflectionPanel.h" +#include "panels/SSGIPanel.h" +#include "panels/SSSPanel.h" #include "panels/VolumetricCloudsPanel.h" #include "panels/IrradianceVolumePanel.h" #include "panels/MaterialPanel.h" +#include "panels/MaterialsPanel.h" #include "panels/GPUProfilerPanel.h" diff --git a/libs/ImguiExtension/UiElements.cpp b/libs/ImguiExtension/UiElements.cpp new file mode 100644 index 000000000..cb253a02f --- /dev/null +++ b/libs/ImguiExtension/UiElements.cpp @@ -0,0 +1,18 @@ +#include "UiElements.h" + +namespace Atlas::ImguiExtension { + + void UIElements::TexturePreview(Ref& wrapper, const Texture::Texture2D& texture) { + + auto lineHeight = ImGui::GetTextLineHeightWithSpacing(); + auto set = wrapper->GetTextureDescriptorSet(texture); + ImGui::Image(set, ImVec2(lineHeight, lineHeight)); + + if (ImGui::IsItemHovered() && ImGui::BeginItemTooltip()) { + ImGui::Image(set, ImVec2(lineHeight * 8.0f, lineHeight * 8.0f)); + ImGui::EndTooltip(); + } + + } + +} \ No newline at end of file diff --git a/libs/ImguiExtension/UiElements.h b/libs/ImguiExtension/UiElements.h new file mode 100644 index 000000000..ed6aa7bc3 --- /dev/null +++ b/libs/ImguiExtension/UiElements.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "System.h" +#include "ImguiWrapper.h" +#include "texture/Texture2D.h" + +namespace Atlas::ImguiExtension { + + class UIElements { + + public: + static void TexturePreview(Ref& wrapper, const Texture::Texture2D& texture); + + }; + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/MaterialPanel.cpp b/libs/ImguiExtension/panels/MaterialPanel.cpp index 92ad7f4fe..ca4da3b9e 100644 --- a/libs/ImguiExtension/panels/MaterialPanel.cpp +++ b/libs/ImguiExtension/panels/MaterialPanel.cpp @@ -2,6 +2,56 @@ namespace Atlas::ImguiExtension { + void MaterialPanel::Render(Ref& wrapper, Ref &material) { + ImGui::PushID(GetNameID()); + + auto availableWidth = ImGui::GetContentRegionAvail().x; + auto padding = 8.0f; + auto widthAfterImage = availableWidth - padding - ImGui::GetTextLineHeight(); + + auto renderWithImagePreview = [&](Ref& texture, std::function element) { + if (texture != nullptr) { + UIElements::TexturePreview(wrapper, *texture); + ImGui::SameLine(); + // Calculate next item width and push a width with the image elements width substracted from it + auto width = ImGui::CalcItemWidth(); + ImGui::PushItemWidth(width - padding - ImGui::GetTextLineHeightWithSpacing()); + } + element(); + if (texture != nullptr) + ImGui::PopItemWidth(); + }; + + renderWithImagePreview(material->baseColorMap, [&]() { + ImGui::ColorEdit3("Base color", glm::value_ptr(material->baseColor)); + }); + ImGui::ColorEdit3("Emissive color", glm::value_ptr(material->emissiveColor)); + ImGui::DragFloat("Emissive intensity", &material->emissiveIntensity, 0.1f, 1.0f, 10000.0f, + "%.2f", ImGuiSliderFlags_Logarithmic); + + renderWithImagePreview(material->opacityMap, [&]() { + ImGui::SliderFloat("Opacity", &material->opacity, 0.0f, 1.0f); + }); + renderWithImagePreview(material->normalMap, [&]() { + ImGui::SliderFloat("Normal scale", &material->normalScale, 0.0f, 1.0f); + }); + renderWithImagePreview(material->roughnessMap, [&]() { + ImGui::SliderFloat("Roughness", &material->roughness, 0.0f, 1.0f); + }); + renderWithImagePreview(material->metalnessMap, [&]() { + ImGui::SliderFloat("Metallic", &material->metalness, 0.0f, 1.0f); + }); + renderWithImagePreview(material->aoMap, [&]() { + ImGui::SliderFloat("Ao", &material->ao, 0.0f, 1.0f); + }); + + ImGui::SliderFloat("Reflectance", &material->reflectance, 0.0f, 1.0f); + + ImGui::Checkbox("Two sided", &material->twoSided); + + ImGui::PopID(); + + } } \ No newline at end of file diff --git a/libs/ImguiExtension/panels/MaterialPanel.h b/libs/ImguiExtension/panels/MaterialPanel.h index e69de29bb..8960d33c5 100644 --- a/libs/ImguiExtension/panels/MaterialPanel.h +++ b/libs/ImguiExtension/panels/MaterialPanel.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Panel.h" + +#include "Material.h" + +namespace Atlas::ImguiExtension { + + class MaterialPanel : public Panel { + + public: + MaterialPanel() : Panel("Material properties") {} + + void Render(Ref& wrapper, Ref& material); + + }; + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/MaterialsPanel.cpp b/libs/ImguiExtension/panels/MaterialsPanel.cpp new file mode 100644 index 000000000..35cce05ff --- /dev/null +++ b/libs/ImguiExtension/panels/MaterialsPanel.cpp @@ -0,0 +1,44 @@ +#include "MaterialsPanel.h" + +#include + +#include + +namespace Atlas::ImguiExtension { + + void MaterialsPanel::Render(Ref& wrapper, std::vector> &materials) { + + ImGui::PushID(GetNameID()); + + ImGui::InputTextWithHint("Search", "Type to search material", &materialSearch); + + auto matches = materials | std::views::filter( + [&](const Ref& material) { + return material->name.find(materialSearch) != std::string::npos; + }); + + int32_t counter = 0; + for (auto& material : matches) { + + ImGui::PushID(counter++); + + auto materialName = !material->name.empty() ? material->name + : "No name " + std::to_string(counter); + + if (ImGui::TreeNode(materialName.c_str())) { + + materialPanel.Render(wrapper, material); + + ImGui::TreePop(); + + } + + ImGui::PopID(); + + } + + ImGui::PopID(); + + } + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/MaterialsPanel.h b/libs/ImguiExtension/panels/MaterialsPanel.h new file mode 100644 index 000000000..61f5cc5b3 --- /dev/null +++ b/libs/ImguiExtension/panels/MaterialsPanel.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Panel.h" + +#include "MaterialPanel.h" + +#include + +namespace Atlas::ImguiExtension { + + class MaterialsPanel : public Panel { + + public: + MaterialsPanel() : Panel("Materials properties") {} + + void Render(Ref& wrapper, std::vector>& materials); + + private: + MaterialPanel materialPanel; + + std::string materialSearch; + + }; + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/Panel.h b/libs/ImguiExtension/panels/Panel.h index f4ea63c6e..fd6a80cb0 100644 --- a/libs/ImguiExtension/panels/Panel.h +++ b/libs/ImguiExtension/panels/Panel.h @@ -3,6 +3,8 @@ #include #include +#include "../UiElements.h" + namespace Atlas::ImguiExtension { class Panel { diff --git a/libs/ImguiExtension/panels/SSGIPanel.cpp b/libs/ImguiExtension/panels/SSGIPanel.cpp new file mode 100644 index 000000000..e8d4892d0 --- /dev/null +++ b/libs/ImguiExtension/panels/SSGIPanel.cpp @@ -0,0 +1,21 @@ +#include "SSGIPanel.h" + +namespace Atlas::ImguiExtension { + + void SSGIPanel::Render(Ref& ssgi) { + + ImGui::PushID(GetNameID()); + + ImGui::Checkbox("Enable", &ssgi->enable); + ImGui::Checkbox("Enable ambient occlusion", &ssgi->enableAo); + ImGui::SliderInt("Ray count", &ssgi->rayCount, 1, 8); + ImGui::SliderInt("Sample count", &ssgi->sampleCount, 1, 16); + ImGui::SliderFloat("Radius", &ssgi->radius, 0.0f, 10.0f); + ImGui::SliderFloat("Ao strength", &ssgi->aoStrength, 0.0f, 10.0f); + ImGui::SliderFloat("Irradiance limit", &ssgi->irradianceLimit, 0.0f, 10.0f); + + ImGui::PopID(); + + } + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/SSGIPanel.h b/libs/ImguiExtension/panels/SSGIPanel.h new file mode 100644 index 000000000..fa9622d58 --- /dev/null +++ b/libs/ImguiExtension/panels/SSGIPanel.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Panel.h" + +#include "lighting/SSGI.h" + +namespace Atlas::ImguiExtension { + + class SSGIPanel : public Panel { + + public: + SSGIPanel() : Panel("SSGI properties") {} + + void Render(Ref& ssgi); + + }; + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/SSSPanel.cpp b/libs/ImguiExtension/panels/SSSPanel.cpp new file mode 100644 index 000000000..fe0a1b8b8 --- /dev/null +++ b/libs/ImguiExtension/panels/SSSPanel.cpp @@ -0,0 +1,18 @@ +#include "SSSPanel.h" + +namespace Atlas::ImguiExtension { + + void SSSPanel::Render(Ref& sss) { + + ImGui::PushID(GetNameID()); + + ImGui::Checkbox("Enable", &sss->enable); + ImGui::SliderInt("Sample count", &sss->sampleCount, 2.0, 16.0); + ImGui::SliderFloat("Max length", &sss->maxLength, 0.01f, 1.0f); + ImGui::SliderFloat("Thickness", &sss->thickness, 0.001f, 1.0f, "%.3f", ImGuiSliderFlags_Logarithmic); + + ImGui::PopID(); + + } + +} \ No newline at end of file diff --git a/libs/ImguiExtension/panels/SSSPanel.h b/libs/ImguiExtension/panels/SSSPanel.h new file mode 100644 index 000000000..54ee557b1 --- /dev/null +++ b/libs/ImguiExtension/panels/SSSPanel.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Panel.h" + +#include "lighting/SSS.h" + +namespace Atlas::ImguiExtension { + + class SSSPanel : public Panel { + + public: + SSSPanel() : Panel("SSS properties") {} + + void Render(Ref& sss); + + }; + +} \ No newline at end of file diff --git a/src/demo/App.cpp b/src/demo/App.cpp index 44e1f9658..ae8af2bcf 100644 --- a/src/demo/App.cpp +++ b/src/demo/App.cpp @@ -77,8 +77,7 @@ void App::LoadContent() { directionalLight.properties.directional.direction = glm::vec3(0.0f, -1.0f, 1.0f); directionalLight.color = glm::vec3(255, 236, 209) / 255.0f; - glm::mat4 orthoProjection = glm::ortho(-100.0f, 100.0f, -70.0f, 120.0f, -120.0f, 120.0f); - directionalLight.AddDirectionalShadow(200.0f, 3.0f, 4096, glm::vec3(0.0f), orthoProjection); + directionalLight.AddDirectionalShadow(200.0f, 3.0f, 4096, glm::vec3(0.0f), glm::vec4(-100.0f, 100.0f, -70.0f, 120.0f)); directionalLight.isMain = false; scene->ao = Atlas::CreateRef(16); @@ -109,16 +108,18 @@ void App::LoadContent() { LoadScene(); + imguiWrapper = Atlas::CreateRef(); + ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - imguiWrapper.Load(&window); + imguiWrapper->Load(&window); } void App::UnloadContent() { UnloadScene(); - imguiWrapper.Unload(); + imguiWrapper->Unload(); } @@ -132,7 +133,7 @@ void App::Update(float deltaTime) { const ImGuiIO& io = ImGui::GetIO(); - imguiWrapper.Update(&window, deltaTime); + imguiWrapper->Update(&window, deltaTime); if (io.WantCaptureMouse) { mouseHandler.lock = true; @@ -437,7 +438,7 @@ void App::Render(float deltaTime) { else graphicsDevice->CreateSwapChain(VK_PRESENT_MODE_IMMEDIATE_KHR, colorSpace); vsyncMode = vsync; hdrMode = hdr; - imguiWrapper.RecreateImGuiResources(); + imguiWrapper->RecreateImGuiResources(); graphicsDevice->WaitForIdle(); recreateSwapchain = true; } @@ -516,23 +517,13 @@ void App::Render(float deltaTime) { } ImGui::SliderFloat("Bias##Shadow", &shadow->bias, 0.0f, 2.0f); } - if (ImGui::CollapsingHeader("Screen-space shadows (preview)")) { + if (ImGui::CollapsingHeader("Screen-space shadows")) { ImGui::Checkbox("Debug##SSS", &debugSSS); - ImGui::Checkbox("Enable##SSS", &sss->enable); - ImGui::SliderInt("Sample count##SSS", &sss->sampleCount, 2.0, 16.0); - ImGui::SliderFloat("Max length##SSS", &sss->maxLength, 0.01f, 1.0f); - ImGui::SliderFloat("Thickness##SSS", &sss->thickness, 0.001f, 1.0f, "%.3f", ImGuiSliderFlags_Logarithmic); + sssPanel.Render(sss); } if (ImGui::CollapsingHeader("SSGI")) { ImGui::Checkbox("Debug##SSGI", &debugSSGI); - ImGui::Checkbox("Enable##SSGI", &ssgi->enable); - ImGui::Checkbox("Enable ambient occlusion##SSGI", &ssgi->enableAo); - ImGui::SliderInt("Ray count##SSGI", &ssgi->rayCount, 1, 8); - ImGui::SliderInt("Sample count##SSGI", &ssgi->sampleCount, 1, 16); - ImGui::SliderFloat("Radius##SSGI", &ssgi->radius, 0.0f, 10.0f); - ImGui::SliderFloat("Ao strength##SSGI", &ssgi->aoStrength, 0.0f, 10.0f); - ImGui::SliderFloat("Irradiance limit##SSGI", &ssgi->irradianceLimit, 0.0f, 10.0f); - //ImGui::SliderInt("Sample count##Ao", &ao->s, 0.0f, 20.0f, "%.3f", 2.0f); + ssgiPanel.Render(ssgi); } if (ImGui::CollapsingHeader("Ambient Occlusion")) { ImGui::Checkbox("Debug##Ao", &debugAo); @@ -598,37 +589,7 @@ void App::Render(float deltaTime) { if (ImGui::CollapsingHeader("Materials")) { int32_t id = 0; auto materials = scene->GetMaterials(); - for (auto material : materials) { - auto label = material->name + "##mat" + std::to_string(id++); - - if (ImGui::TreeNode(label.c_str())) { - auto twoSidedLabel = "Two sided##" + label; - auto baseColorLabel = "Base color##" + label; - auto emissionColorLabel = "Emission color##" + label; - auto emissionPowerLabel = "Emission power##" + label; - auto transmissionColorLabel = "Transmission color##" + label; - - ImGui::Checkbox(twoSidedLabel.c_str(), &material->twoSided); - ImGui::ColorEdit3(baseColorLabel.c_str(), glm::value_ptr(material->baseColor)); - ImGui::ColorEdit3(emissionColorLabel.c_str(), glm::value_ptr(material->emissiveColor)); - ImGui::SliderFloat(emissionPowerLabel.c_str(), &material->emissiveIntensity, 1.0f, 10000.0f, - "%.3f", ImGuiSliderFlags_Logarithmic); - - auto roughnessLabel = "Roughness##" + label; - auto metallicLabel = "Metallic##" + label; - auto reflectanceLabel = "Reflectance##" + label; - auto aoLabel = "Ao##" + label; - auto opacityLabel = "Opacity##" + label; - - ImGui::SliderFloat(roughnessLabel.c_str(), &material->roughness, 0.0f, 1.0f); - ImGui::SliderFloat(metallicLabel.c_str(), &material->metalness, 0.0f, 1.0f); - ImGui::SliderFloat(reflectanceLabel.c_str(), &material->reflectance, 0.0f, 1.0f); - ImGui::SliderFloat(aoLabel.c_str(), &material->ao, 0.0f, 1.0f); - ImGui::SliderFloat(opacityLabel.c_str(), &material->opacity, 0.0f, 1.0f); - - ImGui::TreePop(); - } - } + materialsPanel.Render(imguiWrapper, materials); } if (ImGui::CollapsingHeader("Controls")) { ImGui::Text("Use WASD for movement"); @@ -665,7 +626,7 @@ void App::Render(float deltaTime) { #endif if (!recreateSwapchain) { - imguiWrapper.Render(); + imguiWrapper->Render(); } recreateSwapchain = false; @@ -967,7 +928,7 @@ bool App::LoadScene() { "flying world/scene.gltf", ModelLoader::LoadMesh, false, 2048 ); meshes.push_back(mesh); - transforms.emplace_back(0.01f); + transforms.emplace_back(glm::scale(glm::vec3(0.01f))); // Metalness is set to 0.9f //for (auto& material : mesh.data.materials) material.metalness = 0.0f; @@ -993,22 +954,22 @@ bool App::LoadScene() { "newsponza/main/NewSponza_Main_Blender_glTF.gltf", ModelLoader::LoadMesh, false, 2048 ); meshes.push_back(mesh); - transforms.emplace_back(4.0f); + transforms.emplace_back(glm::scale(glm::vec3(4.0f))); mesh = Atlas::ResourceManager::GetOrLoadResourceWithLoaderAsync( "newsponza/candles/NewSponza_100sOfCandles_glTF_OmniLights.gltf", ModelLoader::LoadMesh, false, 2048 ); meshes.push_back(mesh); - transforms.emplace_back(4.0f); + transforms.emplace_back(glm::scale(glm::vec3(4.0f))); mesh = Atlas::ResourceManager::GetOrLoadResourceWithLoaderAsync( "newsponza/curtains/NewSponza_Curtains_glTF.gltf", ModelLoader::LoadMesh, false, 2048 ); meshes.push_back(mesh); - transforms.emplace_back(4.0f); + transforms.emplace_back(glm::scale(glm::vec3(4.0f))); mesh = Atlas::ResourceManager::GetOrLoadResourceWithLoaderAsync( "newsponza/ivy/NewSponza_IvyGrowth_glTF.gltf", ModelLoader::LoadMesh, false, 2048 ); meshes.push_back(mesh); - transforms.emplace_back(4.0f); + transforms.emplace_back(glm::scale(glm::vec3(4.0f))); // Other scene related settings apart from the mesh directionalLight.properties.directional.direction = glm::vec3(0.0f, -1.0f, 0.33f); @@ -1214,10 +1175,14 @@ void App::CheckLoadScene() { // Add rigid body components to entities (we need to wait for loading to complete to get valid mesh bounds) int32_t entityCount = 0; for (auto& entity : entities) { + const auto& transformComponent = entity.GetComponent(); const auto& meshComponent = entity.GetComponent(); + + auto scale = transformComponent.Decompose().scale; if (entityCount++ == 0) { Atlas::Physics::MeshShapeSettings settings = { - .mesh = meshComponent.mesh + .mesh = meshComponent.mesh, + .scale = scale }; auto shape = Atlas::Physics::ShapesManager::CreateShape(settings); @@ -1230,6 +1195,7 @@ void App::CheckLoadScene() { else { Atlas::Physics::BoundingBoxShapeSettings settings = { .aabb = meshComponent.mesh->data.aabb, + .scale = scale }; auto shape = Atlas::Physics::ShapesManager::CreateShape(settings); diff --git a/src/demo/App.h b/src/demo/App.h index 7efcffc52..00c246f5a 100644 --- a/src/demo/App.h +++ b/src/demo/App.h @@ -88,8 +88,11 @@ class App : public Atlas::EngineInstance { Atlas::ImguiExtension::VolumetricCloudsPanel volumetricCloudsPanel; Atlas::ImguiExtension::IrradianceVolumePanel irradianceVolumePanel; Atlas::ImguiExtension::ReflectionPanel reflectionPanel; + Atlas::ImguiExtension::SSGIPanel ssgiPanel; + Atlas::ImguiExtension::SSSPanel sssPanel; Atlas::ImguiExtension::PostProcessingPanel postProcessingPanel; Atlas::ImguiExtension::GPUProfilerPanel gpuProfilerPanel; + Atlas::ImguiExtension::MaterialsPanel materialsPanel; bool renderUI = true; bool renderEnvProbe = true; @@ -120,7 +123,7 @@ class App : public Atlas::EngineInstance { float shootSpawnRate = 0.1f; float shootVelocity = 5.0f; - Atlas::ImguiExtension::ImguiWrapper imguiWrapper; + Ref imguiWrapper; std::vector> audioStreams; diff --git a/src/editor/App.cpp b/src/editor/App.cpp index 27cc3c2d5..d9e0029aa 100644 --- a/src/editor/App.cpp +++ b/src/editor/App.cpp @@ -33,27 +33,38 @@ namespace Atlas::Editor { 32.0f )->Scale = 0.5f; + SubscribeToResourceEvents(); + Singletons::imguiWrapper = CreateRef(); Singletons::imguiWrapper->Load(&window); + Singletons::config = CreateRef(); + + Serializer::DeserializeConfig(); + + // Everything that needs the config comes afterwards + Singletons::icons = CreateRef(); Singletons::renderTarget = CreateRef(1280, 720); Singletons::pathTraceRenderTarget = CreateRef(1280, 720); Singletons::mainRenderer = mainRenderer; - Singletons::icons = CreateRef(); - Singletons::config = CreateRef(); mouseHandler = Input::MouseHandler(1.5f, 8.0f); keyboardHandler = Input::KeyboardHandler(7.0f, 5.0f); - SubscribeToResourceEvents(); + if (Singletons::config->darkMode) + ImGui::StyleColorsDark(); + else + ImGui::StyleColorsLight(); - Serializer::DeserializeConfig(); - } void App::UnloadContent() { Singletons::imguiWrapper->Unload(); + for (const auto& sceneWindow : sceneWindows) { + Serializer::SerializeSceneWindow(sceneWindow); + } + Serializer::SerializeConfig(); Singletons::Destruct(); @@ -70,7 +81,9 @@ namespace Atlas::Editor { for (size_t i = 0; i < waitToLoadScenes.size(); i++) { if (waitToLoadScenes[i].IsLoaded()) { std::swap(waitToLoadScenes[i], waitToLoadScenes.back()); - sceneWindows.emplace_back(waitToLoadScenes.back(), true); + + // After the scene is loaded we can retrieve the window configuration and deserialize it + sceneWindows.emplace_back(Serializer::DeserializeSceneWindow(waitToLoadScenes.back())); waitToLoadScenes.pop_back(); i--; } @@ -86,15 +99,25 @@ namespace Atlas::Editor { // Find active scene and old scene windows size_t sceneCounter = 0; - for (auto& sceneWindow : sceneWindows) { + for (const auto& sceneWindow : sceneWindows) { + // Delete windows not opened in the config (don't know when this does happen) if (std::find(config->openedScenes.begin(), config->openedScenes.end(), - sceneWindow.scene) == config->openedScenes.end()) { + sceneWindow->scene) == config->openedScenes.end()) { + toBeDeletedSceneWindows.push_back(sceneCounter++); + continue; + } + + // Delete closed windows + if (!sceneWindow->show) { + auto iter = std::find(config->openedScenes.begin(), config->openedScenes.end(), + sceneWindow->scene); + config->openedScenes.erase(iter); toBeDeletedSceneWindows.push_back(sceneCounter++); continue; } - if (sceneWindow.viewportPanel.isFocused) + if (sceneWindow->viewportPanel.isFocused) activeSceneIdx = sceneCounter; sceneCounter++; @@ -103,6 +126,8 @@ namespace Atlas::Editor { // Reverse such that indices remain valid std::reverse(toBeDeletedSceneWindows.begin(), toBeDeletedSceneWindows.end()); for (auto sceneWindowIdx : toBeDeletedSceneWindows) { + // Serialize window before erasing it + Serializer::SerializeSceneWindow(sceneWindows[sceneWindowIdx]); sceneWindows.erase(sceneWindows.begin() + sceneWindowIdx); } @@ -115,16 +140,21 @@ namespace Atlas::Editor { auto& activeSceneWindow = sceneWindows[activeSceneIdx]; - auto cameraEntity = activeSceneWindow.cameraEntity; - if (cameraEntity.IsValid() && activeSceneWindow.viewportPanel.isFocused && - !ImGuizmo::IsUsing() && (!activeSceneWindow.isPlaying || !activeSceneWindow.hasPlayer)) { + auto lockMovement = io.KeysDown[ImGuiKey_LeftCtrl] || io.KeysDown[ImGuiKey_LeftAlt]; + + auto cameraEntity = activeSceneWindow->cameraEntity; + if (cameraEntity.IsValid() && activeSceneWindow->viewportPanel.isFocused && !lockMovement && + !ImGuizmo::IsUsing() && (!activeSceneWindow->isPlaying || !activeSceneWindow->hasPlayer)) { auto& camera = cameraEntity.GetComponent(); + mouseHandler.sensibility = activeSceneWindow->cameraRotationSpeed; + keyboardHandler.speed = activeSceneWindow->cameraMovementSpeed; + mouseHandler.Update(camera, deltaTime); keyboardHandler.Update(camera, deltaTime); } - if (activeSceneWindow.isPlaying) { - auto playerSubset = activeSceneWindow.scene->GetSubset(); + if (activeSceneWindow->isPlaying) { + auto playerSubset = activeSceneWindow->scene->GetSubset(); for (auto entity : playerSubset) { auto camera = entity.TryGetComponent(); if (!camera) @@ -139,10 +169,10 @@ namespace Atlas::Editor { // Update all scenes after input was applied for (auto& sceneWindow : sceneWindows) { - sceneWindow.Update(deltaTime); + sceneWindow->Update(deltaTime); } - ImGuizmo::Enable(activeSceneWindow.needGuizmoEnabled); + ImGuizmo::Enable(activeSceneWindow->needGuizmoEnabled); // Launch BVH builds asynchronously auto buildRTStructure = [&]() { @@ -174,6 +204,8 @@ namespace Atlas::Editor { return; } + auto& config = Singletons::config; + ImGui::NewFrame(); ImGuizmo::BeginFrame(); @@ -216,6 +248,13 @@ namespace Atlas::Editor { } if (ImGui::BeginMenu("View")) { + if (ImGui::MenuItem("Dark mode", nullptr, &config->darkMode)) { + if (config->darkMode) + ImGui::StyleColorsDark(); + else + ImGui::StyleColorsLight(); + } + ImGui::MenuItem("Reset layout", nullptr, &resetDockspaceLayout); ImGui::MenuItem("Show logs", nullptr, &logWindow.show); ImGui::MenuItem("Show content browser", nullptr, &contentBrowserWindow.show); @@ -224,7 +263,6 @@ namespace Atlas::Editor { } if (ImGui::BeginMenu("Renderer")) { - auto& config = Singletons::config; ImGui::MenuItem("Pathtracer", nullptr, &config->pathTrace); ImGui::EndMenu(); } @@ -240,7 +278,7 @@ namespace Atlas::Editor { UI::PopupPanels::Render(); for (auto& sceneWindow : sceneWindows) { - sceneWindow.Render(); + sceneWindow->Render(); } contentBrowserWindow.Render(); @@ -268,7 +306,7 @@ namespace Atlas::Editor { ImGui::DockBuilderSplitNode(dsID, ImGuiDir_Up, 0.8f, &dsIdUp, &dsIdDown); for (auto& sceneWindow : sceneWindows) { - ImGui::DockBuilderDockWindow(sceneWindow.GetNameID(), dsIdUp); + ImGui::DockBuilderDockWindow(sceneWindow->GetNameID(), dsIdUp); } upperDockNodeID = dsIdUp; @@ -289,9 +327,9 @@ namespace Atlas::Editor { contentBrowserWindow.resetDockingLayout = true; logWindow.resetDockingLayout = true; - for (auto& sceneWindow : sceneWindows) { - sceneWindow.resetDockingLayout = true; - sceneWindow.show = true; + for (const auto& sceneWindow : sceneWindows) { + sceneWindow->resetDockingLayout = true; + sceneWindow->show = true; } resetDockspaceLayout = false; diff --git a/src/editor/App.h b/src/editor/App.h index 74f59ccbf..8ad541940 100644 --- a/src/editor/App.h +++ b/src/editor/App.h @@ -45,7 +45,7 @@ namespace Atlas::Editor { UI::ProfilerWindow profilerWindow = UI::ProfilerWindow(false); std::vector> waitToLoadScenes; - std::vector sceneWindows; + std::vector> sceneWindows; size_t activeSceneIdx = 0; diff --git a/src/editor/Config.h b/src/editor/Config.h index c753a30be..ba6001f23 100644 --- a/src/editor/Config.h +++ b/src/editor/Config.h @@ -7,6 +7,7 @@ namespace Atlas::Editor { class Config { public: + bool darkMode = true; bool pathTrace = false; std::vector> openedScenes; diff --git a/src/editor/DataCreator.cpp b/src/editor/DataCreator.cpp index 1e624890d..41726eb8a 100644 --- a/src/editor/DataCreator.cpp +++ b/src/editor/DataCreator.cpp @@ -19,8 +19,7 @@ namespace Atlas::Editor { directionalLight.properties.directional.direction = glm::vec3(0.0f, -1.0f, 1.0f); directionalLight.color = glm::vec3(255, 236, 209) / 255.0f; directionalLight.intensity = 10.0f; - glm::mat4 orthoProjection = glm::ortho(-100.0f, 100.0f, -70.0f, 120.0f, -120.0f, 120.0f); - directionalLight.AddDirectionalShadow(200.0f, 3.0f, 4096, glm::vec3(0.0f), orthoProjection); + directionalLight.AddDirectionalShadow(200.0f, 3.0f, 4096, glm::vec3(0.0f), vec4(-100.0f, 100.0f, -70.0f, 120.0f)); directionalLight.isMain = true; mainHierarchy.AddChild(directionalLightEntity); diff --git a/src/editor/FileImporter.cpp b/src/editor/FileImporter.cpp index 42cd579cc..92b6126f3 100644 --- a/src/editor/FileImporter.cpp +++ b/src/editor/FileImporter.cpp @@ -4,13 +4,6 @@ #include "common/Path.h" -#include "mesh/Mesh.h" -#include "scene/Scene.h" -#include "audio/AudioData.h" - -#include "loader/ModelLoader.h" -#include "scene/SceneSerializer.h" - namespace Atlas::Editor { void FileImporter::ImportFiles(const std::vector &filenames) { @@ -32,30 +25,16 @@ namespace Atlas::Editor { auto type = fileTypeMapping.at(fileType); switch(type) { - case FileType::Audio: { - auto handle = ResourceManager::GetOrLoadResourceAsync( - filename, ResourceOrigin::User); - handle.GetResource()->permanent = true; - } - break; - case FileType::Mesh: { - auto handle = ResourceManager::GetOrLoadResourceWithLoaderAsync(filename, - ResourceOrigin::User, Loader::ModelLoader::LoadMesh, false, 8192); - handle.GetResource()->permanent = true; - } - break; - case FileType::Scene: { - auto handle = ResourceManager::GetOrLoadResourceWithLoaderAsync(filename, - ResourceOrigin::User, Scene::SceneSerializer::DeserializeScene); - } - break; - default: - break; + case FileType::Audio: ImportFile(filename); break; + case FileType::Mesh: ImportFile(filename); break; + case FileType::Scene: ImportFile(filename); break; + case FileType::Font: ImportFile(filename); break; + default: break; } } - const std::map FileImporter::fileTypeMapping = { + const std::map FileImporter::fileTypeMapping = { { "wav", FileType::Audio }, { "gltf", FileType::Mesh }, { "glb", FileType::Mesh }, @@ -63,6 +42,8 @@ namespace Atlas::Editor { { "fbx", FileType::Mesh }, { "aeterrain", FileType::Terrain }, { "aescene", FileType::Scene }, + { "lua", FileType::Script }, + { "ttf", FileType::Font }, }; } \ No newline at end of file diff --git a/src/editor/FileImporter.h b/src/editor/FileImporter.h index db6fc34fc..7dc4dc376 100644 --- a/src/editor/FileImporter.h +++ b/src/editor/FileImporter.h @@ -3,9 +3,29 @@ #include #include #include +#include + +#include "resource/ResourceManager.h" + +#include "mesh/Mesh.h" +#include "scene/Scene.h" +#include "audio/AudioData.h" +#include "Font.h" + +#include "loader/ModelLoader.h" +#include "scene/SceneSerializer.h" namespace Atlas::Editor { + enum class FileType { + Audio = 0, + Mesh, + Terrain, + Scene, + Script, + Font + }; + class FileImporter { public: @@ -13,17 +33,78 @@ namespace Atlas::Editor { static void ImportFile(const std::string& filename); - private: - enum class FileType { - Audio = 0, - Mesh, - Terrain, - Scene - }; + template + static ResourceHandle ImportFile(const std::string& filename); - static const std::map fileTypeMapping; + template + static bool AreCompatible(const std::string& filename); + static const std::map fileTypeMapping; }; + template + ResourceHandle FileImporter::ImportFile(const std::string& filename) { + + ResourceHandle handle; + + if constexpr (std::is_same_v) { + handle = ResourceManager::GetOrLoadResourceAsync( + filename, ResourceOrigin::User); + } + else if constexpr (std::is_same_v) { + handle = ResourceManager::GetOrLoadResourceWithLoaderAsync(filename, + ResourceOrigin::User, Loader::ModelLoader::LoadMesh, false, 8192); + } + else if constexpr (std::is_same_v) { + handle = ResourceManager::GetOrLoadResourceWithLoaderAsync(filename, + ResourceOrigin::User, Scene::SceneSerializer::DeserializeScene); + } + else if constexpr (std::is_same_v) { + // Load script here + } + else if constexpr (std::is_same_v) { + handle = ResourceManager::GetOrLoadResourceAsync(filename, + ResourceOrigin::User, 32.0f, 8, 127); + } + + if (handle.IsValid()) + handle.GetResource()->permanent = true; + + return handle; + + } + + template + bool FileImporter::AreCompatible(const std::string& filename) { + + std::string fileType = Common::Path::GetFileType(filename); + std::transform(fileType.begin(), fileType.end(), fileType.begin(), ::tolower); + + if (!fileTypeMapping.contains(fileType)) + return false; + + auto type = fileTypeMapping.at(fileType); + + if constexpr (std::is_same_v) { + return type == FileType::Audio; + } + else if constexpr (std::is_same_v) { + return type == FileType::Mesh; + } + else if constexpr (std::is_same_v) { + return type == FileType::Scene; + } + else if constexpr (std::is_same_v) { + return type == FileType::Script; + } + else if constexpr (std::is_same_v) { + return type == FileType::Font; + } + else { + return false; + } + + } + } \ No newline at end of file diff --git a/src/editor/Icons.cpp b/src/editor/Icons.cpp index 4e8cb4794..42280fce5 100644 --- a/src/editor/Icons.cpp +++ b/src/editor/Icons.cpp @@ -1,26 +1,21 @@ #include "Icons.h" +#include "Singletons.h" namespace Atlas::Editor { Icons::Icons() { - icons = std::map { - { IconType::Audio, Texture::Texture2D("editor/icons/audio.png") }, - { IconType::Camera, Texture::Texture2D("editor/icons/camera.png") }, - { IconType::Document, Texture::Texture2D("editor/icons/document.png") }, - { IconType::Folder, Texture::Texture2D("editor/icons/folder.png") }, - { IconType::Image, Texture::Texture2D("editor/icons/image.png") }, - { IconType::Mesh, Texture::Texture2D("editor/icons/mesh.png") }, - { IconType::Play, Texture::Texture2D("editor/icons/play.png") }, - { IconType::Stop, Texture::Texture2D("editor/icons/stop.png") }, - { IconType::Scene, Texture::Texture2D("editor/icons/scene.png") }, - { IconType::Terrain, Texture::Texture2D("editor/icons/terrain.png") }, - }; + LoadIcons(); } Texture::Texture2D &Icons::Get(IconType type) { + auto darkMode = Singletons::config->darkMode; + + if (darkMode != darkModeIcons) + LoadIcons(); + if (!icons.contains(type)) return icons[IconType::Document]; @@ -28,4 +23,51 @@ namespace Atlas::Editor { } + void Icons::LoadIcons() { + + auto darkMode = Singletons::config->darkMode; + + if (darkMode) { + icons = std::map{ + { IconType::Audio, Texture::Texture2D("editor/icons/audio.png") }, + { IconType::Camera, Texture::Texture2D("editor/icons/camera.png") }, + { IconType::Document, Texture::Texture2D("editor/icons/document.png") }, + { IconType::Folder, Texture::Texture2D("editor/icons/folder.png") }, + { IconType::Image, Texture::Texture2D("editor/icons/image.png") }, + { IconType::Mesh, Texture::Texture2D("editor/icons/mesh.png") }, + { IconType::Play, Texture::Texture2D("editor/icons/play.png") }, + { IconType::Stop, Texture::Texture2D("editor/icons/stop.png") }, + { IconType::Scene, Texture::Texture2D("editor/icons/scene.png") }, + { IconType::Terrain, Texture::Texture2D("editor/icons/terrain.png") }, + { IconType::Settings, Texture::Texture2D("editor/icons/settings.png") }, + { IconType::Font, Texture::Texture2D("editor/icons/font.png") }, + { IconType::ArrowUp, Texture::Texture2D("editor/icons/arrowUp.png") }, + { IconType::ArrowLeft, Texture::Texture2D("editor/icons/arrowLeft.png") }, + { IconType::ArrowRight, Texture::Texture2D("editor/icons/arrowRight.png") }, + }; + } + else { + icons = std::map{ + { IconType::Audio, Texture::Texture2D("editor/icons/audio_light.png") }, + { IconType::Camera, Texture::Texture2D("editor/icons/camera_light.png") }, + { IconType::Document, Texture::Texture2D("editor/icons/document_light.png") }, + { IconType::Folder, Texture::Texture2D("editor/icons/folder_light.png") }, + { IconType::Image, Texture::Texture2D("editor/icons/image_light.png") }, + { IconType::Mesh, Texture::Texture2D("editor/icons/mesh_light.png") }, + { IconType::Play, Texture::Texture2D("editor/icons/play_light.png") }, + { IconType::Stop, Texture::Texture2D("editor/icons/stop_light.png") }, + { IconType::Scene, Texture::Texture2D("editor/icons/scene_light.png") }, + { IconType::Terrain, Texture::Texture2D("editor/icons/terrain_light.png") }, + { IconType::Settings, Texture::Texture2D("editor/icons/settings_light.png") }, + { IconType::Font, Texture::Texture2D("editor/icons/font_light.png") }, + { IconType::ArrowUp, Texture::Texture2D("editor/icons/arrowUp_light.png") }, + { IconType::ArrowLeft, Texture::Texture2D("editor/icons/arrowLeft_light.png") }, + { IconType::ArrowRight, Texture::Texture2D("editor/icons/arrowRight_light.png") }, + }; + } + + darkModeIcons = darkMode; + + } + } \ No newline at end of file diff --git a/src/editor/Icons.h b/src/editor/Icons.h index 86e745bd6..5f901efad 100644 --- a/src/editor/Icons.h +++ b/src/editor/Icons.h @@ -16,7 +16,12 @@ namespace Atlas::Editor { Play, Stop, Scene, - Terrain + Terrain, + Font, + Settings, + ArrowUp, + ArrowLeft, + ArrowRight }; class Icons { @@ -26,8 +31,12 @@ namespace Atlas::Editor { Texture::Texture2D& Get(IconType type); private: + void LoadIcons(); + std::map icons; + bool darkModeIcons = true; + }; } \ No newline at end of file diff --git a/src/editor/Serializer.cpp b/src/editor/Serializer.cpp index 3addbba2e..63f934a31 100644 --- a/src/editor/Serializer.cpp +++ b/src/editor/Serializer.cpp @@ -1,24 +1,20 @@ #include "Serializer.h" #include "Singletons.h" #include "FileImporter.h" + #include "common/SerializationHelper.h" +#include "scene/EntitySerializer.h" #include "Log.h" #include "loader/AssetLoader.h" namespace Atlas::Editor { - const std::string Serializer::configFilename = "config.json"; + const std::string Serializer::configPath = ".config/"; + const std::string Serializer::configFilename = configPath + "config.json"; void Serializer::SerializeConfig() { - auto fileStream = Loader::AssetLoader::WriteFile(configFilename, std::ios::out | std::ios::binary); - - if (!fileStream.is_open()) { - Log::Error("Couldn't write config file"); - return; - } - const auto& config = Singletons::config; std::vector scenePaths; @@ -26,31 +22,18 @@ namespace Atlas::Editor { scenePaths.push_back(scene.GetResource()->path); json j = { + { "darkMode", config->darkMode }, { "pathTrace", config->pathTrace }, { "scenes", scenePaths } }; - fileStream << to_string(j); - - fileStream.close(); + TryWriteToFile(configFilename, to_string(j)); } void Serializer::DeserializeConfig() { - Loader::AssetLoader::UnpackFile(configFilename); - auto path = Loader::AssetLoader::GetFullPath(configFilename); - - auto fileStream = Loader::AssetLoader::ReadFile(path, std::ios::in | std::ios::binary); - - if (!fileStream.is_open()) { - return; - } - - std::string serialized((std::istreambuf_iterator(fileStream)), - std::istreambuf_iterator()); - - fileStream.close(); + auto serialized = TryReadFromFile(configFilename); if (serialized.empty()) return; @@ -61,9 +44,10 @@ namespace Atlas::Editor { std::vector scenePaths; + if (j.contains("darkMode")) + j.at("darkMode").get_to(config->darkMode); if (j.contains("pathTrace")) j.at("pathTrace").get_to(config->pathTrace); - if (j.contains("scenes")) j.at("scenes").get_to(scenePaths); @@ -73,4 +57,92 @@ namespace Atlas::Editor { } + void Serializer::SerializeSceneWindow(const Ref& sceneWindow) { + + if (!sceneWindow->scene.IsLoaded()) + return; + + json camera; + std::set insertedEntites; + Scene::EntityToJson(camera, sceneWindow->cameraEntity, sceneWindow->scene.Get(), insertedEntites); + + json j = { + { "cameraMovementSpeed", sceneWindow->cameraMovementSpeed }, + { "cameraRotationSpeed", sceneWindow->cameraRotationSpeed }, + { "camera", camera } + }; + + auto filename = configPath + sceneWindow->scene->name + "WindowConfig.json"; + + TryWriteToFile(filename, to_string(j)); + + } + + Ref Serializer::DeserializeSceneWindow(ResourceHandle handle) { + + if (!handle.IsLoaded()) + return nullptr; + + auto filename = configPath + handle->name + "WindowConfig.json"; + + auto sceneWindow = CreateRef(handle, true); + + auto serialized = TryReadFromFile(filename); + + if (serialized.empty()) + return sceneWindow; + + json j = json::parse(serialized); + + json camera; + + if (j.contains("cameraMovementSpeed")) + j.at("cameraMovementSpeed").get_to(sceneWindow->cameraMovementSpeed); + if (j.contains("cameraRotationSpeed")) + j.at("cameraRotationSpeed").get_to(sceneWindow->cameraRotationSpeed); + if (j.contains("camera")) + j.at("camera").get_to(camera); + + sceneWindow->cameraEntity = sceneWindow->scene->CreateEntity(); + Scene::EntityFromJson(camera, sceneWindow->cameraEntity, sceneWindow->scene.Get()); + + return sceneWindow; + + } + + void Serializer::TryWriteToFile(const std::string& filename, const std::string& content) { + + auto fileStream = Loader::AssetLoader::WriteFile(filename, std::ios::out | std::ios::binary); + + if (!fileStream.is_open()) { + Log::Error("Couldn't write config file"); + return; + } + + fileStream << content; + + fileStream.close(); + + } + + std::string Serializer::TryReadFromFile(const std::string& filename) { + + Loader::AssetLoader::UnpackFile(filename); + auto path = Loader::AssetLoader::GetFullPath(filename); + + auto fileStream = Loader::AssetLoader::ReadFile(path, std::ios::in | std::ios::binary); + + if (!fileStream.is_open()) { + return std::string(); + } + + std::string content((std::istreambuf_iterator(fileStream)), + std::istreambuf_iterator()); + + fileStream.close(); + + return content; + + } + } \ No newline at end of file diff --git a/src/editor/Serializer.h b/src/editor/Serializer.h index 53187e582..7dde5009b 100644 --- a/src/editor/Serializer.h +++ b/src/editor/Serializer.h @@ -2,6 +2,8 @@ #include +#include "ui/windows/SceneWindow.h" + namespace Atlas::Editor { class Serializer { @@ -11,9 +13,19 @@ namespace Atlas::Editor { static void DeserializeConfig(); + static void SerializeSceneWindow(const Ref& sceneWindow); + + static Ref DeserializeSceneWindow(ResourceHandle handle); + private: + static void TryWriteToFile(const std::string& filename, const std::string& content); + + static std::string TryReadFromFile(const std::string& filename); + static const std::string configFilename; + static const std::string configPath; + }; } \ No newline at end of file diff --git a/src/editor/tools/PrimitiveBatchWrapper.cpp b/src/editor/tools/PrimitiveBatchWrapper.cpp index 59f0b8584..3daac736c 100644 --- a/src/editor/tools/PrimitiveBatchWrapper.cpp +++ b/src/editor/tools/PrimitiveBatchWrapper.cpp @@ -53,4 +53,54 @@ namespace Atlas::Editor { } + void PrimitiveBatchWrapper::RenderLineSphere(vec3 position, float radius, vec3 color) { + + const int32_t verticalSubdivs = 20; + const int32_t horizontalSubdivs = 20; + + vec3 lastPoint; + + float alpha = 0.0f; + + for (int32_t i = 0; i < horizontalSubdivs; i++) { + + float ringRadius = sinf(alpha); + float ringHeight = cosf(alpha); + float beta = 0.0f; + + for (int32_t j = 0; j < verticalSubdivs; j++) { + + float x = sinf(beta) * ringRadius; + float z = cosf(beta) * ringRadius; + auto point = vec3(x, ringHeight, z) * radius + position; + + if (j > 0) { + primitiveBatch->AddLine(point, lastPoint, color, color); + } + + beta += 2.0f * glm::pi() / float(verticalSubdivs - 1); + + lastPoint = point; + + } + + alpha += glm::pi() / float(horizontalSubdivs); + + } + + } + + void PrimitiveBatchWrapper::RenderLineRectangle(const Volume::Rectangle& rect, vec3 color) { + + auto point = rect.point; + auto right = rect.s0; + auto down = rect.s1; + + primitiveBatch->AddLine(point, point + right, color, color); + primitiveBatch->AddLine(point, point + down, color, color); + primitiveBatch->AddLine(point + right, point + right + down, color, color); + primitiveBatch->AddLine(point + down, point + right + down, color, color); + + } + } \ No newline at end of file diff --git a/src/editor/tools/PrimitiveBatchWrapper.h b/src/editor/tools/PrimitiveBatchWrapper.h index 1797a63e5..2373e8011 100644 --- a/src/editor/tools/PrimitiveBatchWrapper.h +++ b/src/editor/tools/PrimitiveBatchWrapper.h @@ -3,6 +3,7 @@ #include "renderer/PrimitiveBatch.h" #include "volume/AABB.h" #include "volume/Frustum.h" +#include "volume/Rectangle.h" namespace Atlas::Editor { @@ -15,6 +16,10 @@ namespace Atlas::Editor { void RenderLineFrustum(Volume::Frustum frustum, vec3 color); + void RenderLineSphere(vec3 point, float radius, vec3 color); + + void RenderLineRectangle(const Volume::Rectangle& rect, vec3 color); + Ref primitiveBatch; }; diff --git a/src/editor/tools/ResourcePayloadHelper.h b/src/editor/tools/ResourcePayloadHelper.h new file mode 100644 index 000000000..6964002c7 --- /dev/null +++ b/src/editor/tools/ResourcePayloadHelper.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../FileImporter.h" + +#include "resource/ResourceManager.h" + +namespace Atlas::Editor { + + class ResourcePayloadHelper { + + public: + template + static ResourceHandle AcceptDropResource() { + + ResourceHandle handle; + + if (ImGui::BeginDragDropTarget()) { + auto dropPayload = ImGui::GetDragDropPayload(); + + bool compatible = false; + + std::string resourcePath; + if (dropPayload->IsDataType("ContentBrowserResource")) { + resourcePath.resize(dropPayload->DataSize); + + // Pretty dangerous + resourcePath = std::string(reinterpret_cast(dropPayload->Data)); + + compatible = FileImporter::AreCompatible(resourcePath); + } + + if (compatible && ImGui::AcceptDragDropPayload("ContentBrowserResource")) { + handle = FileImporter::ImportFile(resourcePath); + } + + ImGui::EndDragDropTarget(); + } + + return handle; + + } + + }; + +} \ No newline at end of file diff --git a/src/editor/ui/panels/EntityPropertiesPanel.cpp b/src/editor/ui/panels/EntityPropertiesPanel.cpp index 39dc2eda3..3236b72e6 100644 --- a/src/editor/ui/panels/EntityPropertiesPanel.cpp +++ b/src/editor/ui/panels/EntityPropertiesPanel.cpp @@ -33,12 +33,28 @@ namespace Atlas::Editor::UI { lightComponentPanel, entity.GetComponent()); } + if (entity.HasComponent()) { + auto& comp = entity.GetComponent(); + RenderComponentPanel("Audio component", scene, + entity, audioComponentPanel, comp); + } + if (entity.HasComponent()) { - auto comp = entity.GetComponent(); + auto& comp = entity.GetComponent(); RenderComponentPanel("Audio volume component", scene, entity, audioVolumeComponentPanel, comp); - entity.RemoveComponent(); - entity.AddComponent(comp); + } + + if (entity.HasComponent()) { + auto& comp = entity.GetComponent(); + RenderComponentPanel("Camera component", scene, + entity, cameraComponentPanel, comp); + } + + if (entity.HasComponent()) { + auto& comp = entity.GetComponent(); + RenderComponentPanel("Text component", scene, + entity, textComponentPanel, comp); } if (entity.HasComponent()) { @@ -63,12 +79,6 @@ namespace Atlas::Editor::UI { newComp.fastVelocity = comp.fastVelocity; newComp.jumpVelocity = comp.jumpVelocity; } - - if (entity.HasComponent()) { - auto& comp = entity.GetComponent(); - RenderComponentPanel("Camera component", scene, - entity, cameraComponentPanel, comp); - } } // Add components @@ -83,10 +93,14 @@ namespace Atlas::Editor::UI { entity.AddComponent(mat4(1.0f), false); if (!entity.HasComponent() && ImGui::MenuItem("Add mesh component")) entity.AddComponent(); + if (!entity.HasComponent() && ImGui::MenuItem("Add audio component")) + entity.AddComponent(); if (!entity.HasComponent() && ImGui::MenuItem("Add audio volume component")) entity.AddComponent(); if (!entity.HasComponent() && ImGui::MenuItem("Add camera component")) entity.AddComponent(); + if (!entity.HasComponent() && ImGui::MenuItem("Add text component")) + entity.AddComponent(); // Just make the player component addable if there is a transform component if (entity.HasComponent() && @@ -130,10 +144,14 @@ namespace Atlas::Editor::UI { entity.RemoveComponent(); if (entity.HasComponent() && ImGui::MenuItem("Remove mesh component")) entity.RemoveComponent(); + if (entity.HasComponent() && ImGui::MenuItem("Remove audio component")) + entity.RemoveComponent(); if (entity.HasComponent() && ImGui::MenuItem("Remove audio volume component")) entity.RemoveComponent(); if (entity.HasComponent() && ImGui::MenuItem("Remove camera component")) entity.RemoveComponent(); + if (entity.HasComponent() && ImGui::MenuItem("Remove text component")) + entity.RemoveComponent(); if (entity.HasComponent() && ImGui::MenuItem("Remove player component")) entity.RemoveComponent(); if (entity.HasComponent() && ImGui::MenuItem("Remove rigid body component")) diff --git a/src/editor/ui/panels/EntityPropertiesPanel.h b/src/editor/ui/panels/EntityPropertiesPanel.h index d6d86d2ad..4e6591bd5 100644 --- a/src/editor/ui/panels/EntityPropertiesPanel.h +++ b/src/editor/ui/panels/EntityPropertiesPanel.h @@ -7,10 +7,12 @@ #include "components/TransformComponentPanel.h" #include "components/MeshComponentPanel.h" #include "components/LightComponentPanel.h" +#include "components/AudioComponentPanel.h" #include "components/AudioVolumeComponentPanel.h" #include "components/RigidBodyComponentPanel.h" #include "components/PlayerComponentPanel.h" #include "components/CameraComponentPanel.h" +#include "components/TextComponentPanel.h" #include @@ -28,10 +30,12 @@ namespace Atlas::Editor::UI { TransformComponentPanel transformComponentPanel; MeshComponentPanel meshComponentPanel; LightComponentPanel lightComponentPanel; + AudioComponentPanel audioComponentPanel; AudioVolumeComponentPanel audioVolumeComponentPanel; RigidBodyComponentPanel rigidBodyComponentPanel; PlayerComponentPanel playerComponentPanel; CameraComponentPanel cameraComponentPanel; + TextComponentPanel textComponentPanel; template bool RenderComponentPanel(const std::string& name, Ref& scene, diff --git a/src/editor/ui/panels/SceneHierarchyPanel.cpp b/src/editor/ui/panels/SceneHierarchyPanel.cpp index 02b61c239..4574516dd 100644 --- a/src/editor/ui/panels/SceneHierarchyPanel.cpp +++ b/src/editor/ui/panels/SceneHierarchyPanel.cpp @@ -1,6 +1,7 @@ #include "SceneHierarchyPanel.h" #include +#include #include namespace Atlas::Editor::UI { @@ -35,6 +36,8 @@ namespace Atlas::Editor::UI { ImGui::EndPopup(); } + ImGui::InputTextWithHint("Search", "Type to search for entity", &entitySearch); + TraverseHierarchy(scene, root, inFocus); RenderExtendedHierarchy(scene); @@ -59,6 +62,10 @@ namespace Atlas::Editor::UI { std::string nodeName = nameComponent ? nameComponent->name : "Entity " + std::to_string(entity); + // If we have a search term and the name doesn't match, return + if (nodeName != "Root" && !entitySearch.empty() && nodeName.find(entitySearch) == std::string::npos) + return; + auto nodeFlags = baseFlags; nodeFlags |= entity == selectedEntity ? ImGuiTreeNodeFlags_Selected : 0; auto entityId = static_cast(entity); @@ -225,6 +232,10 @@ namespace Atlas::Editor::UI { RenderExtendedItem("Irradiance volume", &selectedProperty.irradianceVolume); if (scene->reflection) RenderExtendedItem("Reflection", &selectedProperty.reflection); + if (scene->ssgi) + RenderExtendedItem("Screen-space global illumination", &selectedProperty.ssgi); + if (scene->sss) + RenderExtendedItem("Screen-space shadows", &selectedProperty.sss); RenderExtendedItem("Post processing", &selectedProperty.postProcessing); } diff --git a/src/editor/ui/panels/SceneHierarchyPanel.h b/src/editor/ui/panels/SceneHierarchyPanel.h index 9078a3d4d..f0fce16e6 100644 --- a/src/editor/ui/panels/SceneHierarchyPanel.h +++ b/src/editor/ui/panels/SceneHierarchyPanel.h @@ -10,6 +10,8 @@ namespace Atlas::Editor::UI { bool fog = false; bool volumetricClouds = false; bool reflection = false; + bool ssgi = false; + bool sss = false; bool irradianceVolume = false; bool postProcessing = false; }; @@ -31,6 +33,8 @@ namespace Atlas::Editor::UI { void RenderExtendedItem(const std::string& name, bool* selected); + std::string entitySearch; + }; } \ No newline at end of file diff --git a/src/editor/ui/panels/ScenePropertiesPanel.h b/src/editor/ui/panels/ScenePropertiesPanel.h index 9a8a368b4..159583495 100644 --- a/src/editor/ui/panels/ScenePropertiesPanel.h +++ b/src/editor/ui/panels/ScenePropertiesPanel.h @@ -44,6 +44,14 @@ namespace Atlas::Editor::UI { RenderHeading("Reflection"); reflectionPanel.Render(t); } + else if constexpr (std::is_same_v>) { + RenderHeading("Screen-space global illumination"); + ssgiPanel.Render(t); + } + else if constexpr (std::is_same_v>) { + RenderHeading("Screen-space shadows"); + sssPanel.Render(t); + } else if constexpr (std::is_same_v) { RenderHeading("Post processing"); postProcessingPanel.Render(t); @@ -72,6 +80,8 @@ namespace Atlas::Editor::UI { ImguiExtension::VolumetricCloudsPanel volumetricCloudsPanel; ImguiExtension::IrradianceVolumePanel irradianceVolumePanel; ImguiExtension::ReflectionPanel reflectionPanel; + ImguiExtension::SSGIPanel ssgiPanel; + ImguiExtension::SSSPanel sssPanel; ImguiExtension::PostProcessingPanel postProcessingPanel; }; diff --git a/src/editor/ui/panels/components/AudioComponentPanel.cpp b/src/editor/ui/panels/components/AudioComponentPanel.cpp new file mode 100644 index 000000000..c198f4001 --- /dev/null +++ b/src/editor/ui/panels/components/AudioComponentPanel.cpp @@ -0,0 +1,38 @@ +#include "AudioComponentPanel.h" + +#include "../../../tools/ResourcePayloadHelper.h" + +namespace Atlas::Editor::UI { + + bool AudioComponentPanel::Render(Ref& scene, + Scene::Entity entity, AudioComponent &audioComponent) { + + bool resourceChanged = false; + + Ref> resource; + if (audioComponent.stream && audioComponent.stream->IsValid()) + resource = audioComponent.stream->data.GetResource(); + auto buttonName = resource != nullptr ? resource->GetFileName() : "Drop resource here"; + + ImGui::Button(buttonName.c_str(), {-FLT_MIN, 0}); + + auto handle = ResourcePayloadHelper::AcceptDropResource(); + if (handle.IsValid()) { + audioComponent.ChangeResource(handle); + resourceChanged = true; + } + + ImGui::DragFloat("Volume", &audioComponent.volume, 0.005f, 0.0f, 1.0f); + ImGui::DragFloat("Cutoff", &audioComponent.cutoff, 0.001f, 0.0001f, 0.2f); + ImGui::DragFloat("Falloff factor", &audioComponent.falloffFactor, 0.05f, 0.0f, 100.0f); + ImGui::DragFloat("Falloff power", &audioComponent.falloffPower, 1.0f, 0.0f, 100.0f); + + if (audioComponent.stream && audioComponent.stream->IsValid()) { + ImGui::Checkbox("Loop stream", &audioComponent.stream->loop); + } + + return resourceChanged; + + } + +} \ No newline at end of file diff --git a/src/editor/ui/panels/components/AudioComponentPanel.h b/src/editor/ui/panels/components/AudioComponentPanel.h new file mode 100644 index 000000000..f9405e340 --- /dev/null +++ b/src/editor/ui/panels/components/AudioComponentPanel.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Panel.h" + +#include "scene/Scene.h" +#include "scene/components/AudioComponent.h" + +namespace Atlas::Editor::UI { + + class AudioComponentPanel : public Panel { + + public: + AudioComponentPanel() : Panel("Audio component") {} + + bool Render(Ref& scene, Scene::Entity entity, AudioComponent& audioVolumeComponent); + + }; + +} diff --git a/src/editor/ui/panels/components/AudioVolumeComponentPanel.cpp b/src/editor/ui/panels/components/AudioVolumeComponentPanel.cpp index 100b41538..a4e9161eb 100644 --- a/src/editor/ui/panels/components/AudioVolumeComponentPanel.cpp +++ b/src/editor/ui/panels/components/AudioVolumeComponentPanel.cpp @@ -1,6 +1,6 @@ #include "AudioVolumeComponentPanel.h" -#include "resource/ResourceManager.h" +#include "../../../tools/ResourcePayloadHelper.h" namespace Atlas::Editor::UI { @@ -16,16 +16,10 @@ namespace Atlas::Editor::UI { ImGui::Button(buttonName.c_str(), {-FLT_MIN, 0}); - if (ImGui::BeginDragDropTarget()) { - if (auto dropPayload = ImGui::AcceptDragDropPayload(typeid(Audio::AudioData).name())) { - Resource* dropResource; - std::memcpy(&dropResource, dropPayload->Data, dropPayload->DataSize); - // We know this resource is loaded, so we can just request a handle without loading - audioVolumeComponent.ChangeResource(ResourceManager::GetResource(dropResource->path)); - resourceChanged = true; - } - - ImGui::EndDragDropTarget(); + auto handle = ResourcePayloadHelper::AcceptDropResource(); + if (handle.IsValid()) { + audioVolumeComponent.ChangeResource(handle); + resourceChanged = true; } ImGui::Text("AABB"); @@ -33,8 +27,13 @@ namespace Atlas::Editor::UI { ImGui::DragFloat3("Max", glm::value_ptr(audioVolumeComponent.aabb.max), 0.1f, -10000.0f, 10000.0f); ImGui::DragFloat("Volume", &audioVolumeComponent.volume, 0.005f, 0.0f, 1.0f); - ImGui::DragFloat("Cutoff", &audioVolumeComponent.cutoff, 0.001f, 0.0f, 0.2f); - ImGui::DragFloat("Falloff factor", &audioVolumeComponent.falloffFactor, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Cutoff", &audioVolumeComponent.cutoff, 0.001f, 0.0001f, 0.2f); + ImGui::DragFloat("Falloff factor", &audioVolumeComponent.falloffFactor, 0.05f, 0.0f, 100.0f); + ImGui::DragFloat("Falloff power", &audioVolumeComponent.falloffPower, 1.0f, 0.0f, 100.0f); + + if (audioVolumeComponent.stream && audioVolumeComponent.stream->IsValid()) { + ImGui::Checkbox("Loop stream", &audioVolumeComponent.stream->loop); + } return resourceChanged; diff --git a/src/editor/ui/panels/components/CameraComponentPanel.h b/src/editor/ui/panels/components/CameraComponentPanel.h index 261a19240..43ab1c852 100644 --- a/src/editor/ui/panels/components/CameraComponentPanel.h +++ b/src/editor/ui/panels/components/CameraComponentPanel.h @@ -15,11 +15,6 @@ namespace Atlas::Editor::UI { bool Render(Ref& scene, Scene::Entity entity, CameraComponent& cameraComponent); - private: - void RenderShapeSettings(Scene::Entity entity, Physics::BodyCreationSettings& creationSettings); - - void RenderBodySettings(Scene::Entity entity, Physics::BodyCreationSettings& creationSettings); - }; } \ No newline at end of file diff --git a/src/editor/ui/panels/components/LightComponentPanel.cpp b/src/editor/ui/panels/components/LightComponentPanel.cpp index 4f0ce445a..39aec7814 100644 --- a/src/editor/ui/panels/components/LightComponentPanel.cpp +++ b/src/editor/ui/panels/components/LightComponentPanel.cpp @@ -10,9 +10,9 @@ namespace Atlas::Editor::UI { Scene::Entity entity, LightComponent &lightComponent) { const char* typeItems[] = { "Directional" }; - int currentItem = static_cast(lightComponent.type); - ImGui::Combo("Light type", ¤tItem, typeItems, IM_ARRAYSIZE(typeItems)); - lightComponent.type = static_cast(currentItem); + int typeItem = static_cast(lightComponent.type); + ImGui::Combo("Light type", &typeItem, typeItems, IM_ARRAYSIZE(typeItems)); + lightComponent.type = static_cast(typeItem); bool isStatic = lightComponent.mobility == LightMobility::StationaryLight; ImGui::Checkbox("Static", &isStatic); @@ -41,26 +41,91 @@ namespace Atlas::Editor::UI { ImGui::DragFloat("Intensity", &lightComponent.intensity, 0.1f, 0.0f, 1000.0f); if (!lightComponent.shadow && castShadow) { - //lightComponent.AddDirectionalShadow(300.0f, 3.0f, 1024, 3, 0.95f, true, 2048.0f); if (lightComponent.type == LightType::DirectionalLight) { lightComponent.AddDirectionalShadow(300.0f, 3.0f, 1024, 3, 0.95f, true, 2048.0f); } } else if (lightComponent.shadow && !castShadow) { - lightComponent.AddDirectionalShadow(300.0f, 3.0f, 1024, 3, 0.95f, true, 2048.0f); + lightComponent.shadow = nullptr; } if (castShadow) { + auto& shadow = lightComponent.shadow; + ImGui::Separator(); ImGui::Text("Shadow"); - ImGui::SliderFloat("Bias##Shadow", &lightComponent.shadow->bias, 0.0f, 3.0f); + const char* shadowResItems[] = { "512x512", "1024x1024", "2048x2048", "4096x4096", "8192x8192" }; + int shadowResItem = 0; + if (shadow->resolution == 512) shadowResItem = 0; + if (shadow->resolution == 1024) shadowResItem = 1; + if (shadow->resolution == 2048) shadowResItem = 2; + if (shadow->resolution == 4096) shadowResItem = 3; + if (shadow->resolution == 8192) shadowResItem = 4; + auto prevItem = shadowResItem; + ImGui::Combo("Resolution", &shadowResItem, shadowResItems, IM_ARRAYSIZE(shadowResItems)); + + if (shadowResItem != prevItem) { + switch (shadowResItem) { + case 0: shadow->SetResolution(512); break; + case 1: shadow->SetResolution(1024); break; + case 2: shadow->SetResolution(2048); break; + case 3: shadow->SetResolution(4096); break; + case 4: shadow->SetResolution(8192); break; + } + } + + ImGui::DragFloat("Bias", &shadow->bias, 0.05f, 0.0f, 10.0f); + ImGui::DragFloat("Distance", &shadow->distance, 1.0f, 0.0f, 10000.0f); + + ImGui::Separator(); + + if (lightComponent.type == LightType::DirectionalLight) { + ImGui::Checkbox("Is cascaded", &shadow->isCascaded); + + if (!shadow->isCascaded) { + ImGui::Checkbox("Follow main camera", &shadow->followMainCamera); + + if (!shadow->followMainCamera) { + ImGui::DragFloat3("Center point", glm::value_ptr(shadow->center), 0.5f, -10000.0f, 10000.0f); + } + + auto orthoSize = shadow->views.front().orthoSize; + if (orthoSize.x == 0.0f && orthoSize.y == 0.0f) + orthoSize = vec4(-100.0f, 100.0f, -100.0f, 100.0f); + + ImGui::Text("Width"); + ImGui::DragFloat("Min##Width", &orthoSize.x, 1.0f, -10000.0f, 10000.0f); + ImGui::DragFloat("Max##Width", &orthoSize.y, 1.0f, -10000.0f, 10000.0f); + + ImGui::Text("Height"); + ImGui::DragFloat("Min##Height", &orthoSize.z, 1.0f, -10000.0f, 10000.0f); + ImGui::DragFloat("Max##Height", &orthoSize.w, 1.0f, -10000.0f, 10000.0f); + + auto matrix = glm::ortho(-200.0f, 200.0f, -200.0f, 200.0f, -120.0f, 120.0f); + lightComponent.AddDirectionalShadow(shadow->distance, shadow->bias, shadow->resolution, + shadow->center, orthoSize); + } + else { + ImGui::SliderInt("Cascade count", &shadow->viewCount, 1, 5); + ImGui::DragFloat("Split correction", &shadow->splitCorrection, 0.01f, 0.0f, 1.0f); + ImGui::Checkbox("Long range", &shadow->longRange); + ImGui::DragFloat("Long range distance", &shadow->longRangeDistance, 10.0f, 10.0f, 10000.0f); + + lightComponent.AddDirectionalShadow(shadow->distance, shadow->bias, shadow->resolution, + shadow->viewCount, shadow->splitCorrection, shadow->longRange, shadow->longRangeDistance); + } + } + else if (lightComponent.type == LightType::PointLight) { - } + } + + + } return false; diff --git a/src/editor/ui/panels/components/MeshComponentPanel.cpp b/src/editor/ui/panels/components/MeshComponentPanel.cpp index 6e4d01967..c8e5b6410 100644 --- a/src/editor/ui/panels/components/MeshComponentPanel.cpp +++ b/src/editor/ui/panels/components/MeshComponentPanel.cpp @@ -1,5 +1,7 @@ #include "MeshComponentPanel.h" -#include "resource/ResourceManager.h" + +#include "../../../Singletons.h" +#include "../../../tools/ResourcePayloadHelper.h" #include @@ -11,19 +13,13 @@ namespace Atlas::Editor::UI { bool resourceChanged = false; auto buttonName = meshComponent.mesh.IsValid() ? meshComponent.mesh.GetResource()->GetFileName() : - "Drop resource here"; + "Drop mesh resource here"; ImGui::Button(buttonName.c_str(), {-FLT_MIN, 0}); - if (ImGui::BeginDragDropTarget()) { - if (auto dropPayload = ImGui::AcceptDragDropPayload(typeid(Mesh::Mesh).name())) { - Resource* resource; - std::memcpy(&resource, dropPayload->Data, dropPayload->DataSize); - // We know this mesh is loaded, so we can just request a handle without loading - meshComponent.mesh = ResourceManager::GetResource(resource->path); - resourceChanged = true; - } - - ImGui::EndDragDropTarget(); + auto handle = ResourcePayloadHelper::AcceptDropResource(); + if (handle.IsValid()) { + meshComponent.mesh = handle; + resourceChanged = true; } ImGui::Checkbox("Visible", &meshComponent.visible); @@ -35,6 +31,9 @@ namespace Atlas::Editor::UI { ImGui::Text("Mesh settings"); ImGui::Checkbox("Invert UVs", &mesh->invertUVs); ImGui::Checkbox("Cull backfaces", &mesh->cullBackFaces); + ImGui::Separator(); + ImGui::Text("Materials"); + materialsPanel.Render(Singletons::imguiWrapper, mesh->data.materials); } return resourceChanged; diff --git a/src/editor/ui/panels/components/MeshComponentPanel.h b/src/editor/ui/panels/components/MeshComponentPanel.h index b87a1637e..c154de55b 100644 --- a/src/editor/ui/panels/components/MeshComponentPanel.h +++ b/src/editor/ui/panels/components/MeshComponentPanel.h @@ -6,6 +6,8 @@ #include "scene/Entity.h" #include "scene/components/MeshComponent.h" +#include "ImguiExtension/panels/MaterialsPanel.h" + namespace Atlas::Editor::UI { class MeshComponentPanel : public Panel { @@ -15,6 +17,9 @@ namespace Atlas::Editor::UI { bool Render(Ref& scene, Scene::Entity entity, MeshComponent& meshComponent); + private: + ImguiExtension::MaterialsPanel materialsPanel; + }; } \ No newline at end of file diff --git a/src/editor/ui/panels/components/PlayerComponentPanel.cpp b/src/editor/ui/panels/components/PlayerComponentPanel.cpp index c0dc6bf77..94bfc3501 100644 --- a/src/editor/ui/panels/components/PlayerComponentPanel.cpp +++ b/src/editor/ui/panels/components/PlayerComponentPanel.cpp @@ -63,6 +63,8 @@ namespace Atlas::Editor::UI { ImGui::DragFloat("Fast movement velocity", &player.fastVelocity, 0.1f, 0.0f, 100.0f); ImGui::DragFloat("Jump velocity", &player.jumpVelocity, 0.1f, 0.0f, 100.0f); + ImGui::Checkbox("Allow input", &player.allowInput); + } } \ No newline at end of file diff --git a/src/editor/ui/panels/components/TextComponentPanel.cpp b/src/editor/ui/panels/components/TextComponentPanel.cpp new file mode 100644 index 000000000..cd479be93 --- /dev/null +++ b/src/editor/ui/panels/components/TextComponentPanel.cpp @@ -0,0 +1,43 @@ +#include "TextComponentPanel.h" + +#include + +#include "../../../tools/ResourcePayloadHelper.h" + +namespace Atlas::Editor::UI { + + bool TextComponentPanel::Render(Ref& scene, Scene::Entity entity, + TextComponent& textComponent) { + + ImGui::PushID(GetNameID()); + + auto buttonName = textComponent.font.IsValid() ? textComponent.font.GetResource()->GetFileName() : + "Drop font resource here"; + ImGui::Button(buttonName.c_str(), {-FLT_MIN, 0}); + + auto handle = ResourcePayloadHelper::AcceptDropResource(); + if (handle.IsValid()) { + textComponent.ChangeResource(handle); + } + + ImGui::InputTextMultiline("Text", &textComponent.text); + + ImGui::Text("General properties"); + + ImGui::DragFloat3("Position", glm::value_ptr(textComponent.position), 0.1f); + ImGui::DragFloat2("Half size", glm::value_ptr(textComponent.halfSize), 0.1f); + + ImGui::Text("Text properties"); + + ImGui::DragFloat("Scale", &textComponent.textScale, 0.01f, 0.0f, 100.0f); + ImGui::ColorEdit4("Color", glm::value_ptr(textComponent.textColor)); + ImGui::ColorEdit4("Outline color", glm::value_ptr(textComponent.outlineColor)); + ImGui::DragFloat("Outline factor", &textComponent.outlineFactor, 0.01f, 0.0f, 1.0f); + + ImGui::PopID(); + + return false; + + } + +} \ No newline at end of file diff --git a/src/editor/ui/panels/components/TextComponentPanel.h b/src/editor/ui/panels/components/TextComponentPanel.h new file mode 100644 index 000000000..0660b31aa --- /dev/null +++ b/src/editor/ui/panels/components/TextComponentPanel.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Panel.h" + +#include "scene/Scene.h" +#include "scene/Entity.h" +#include "scene/components/TextComponent.h" + +namespace Atlas::Editor::UI { + + class TextComponentPanel : public Panel { + + public: + TextComponentPanel() : Panel("Text component") {} + + bool Render(Ref& scene, Scene::Entity entity, TextComponent& textComponent); + + }; + +} \ No newline at end of file diff --git a/src/editor/ui/windows/ContentBrowserWindow.cpp b/src/editor/ui/windows/ContentBrowserWindow.cpp index dbb3b2c2d..9c3acf979 100644 --- a/src/editor/ui/windows/ContentBrowserWindow.cpp +++ b/src/editor/ui/windows/ContentBrowserWindow.cpp @@ -1,10 +1,19 @@ -#include #include "ContentBrowserWindow.h" +#include "FileImporter.h" #include "mesh/Mesh.h" #include "scene/Scene.h" #include "audio/AudioData.h" +#include +#include +#include + +#ifdef AE_OS_WINDOWS +#include +#include +#endif + namespace Atlas::Editor::UI { ContentBrowserWindow::ContentBrowserWindow(bool show) : Window("Object browser", show) { @@ -45,39 +54,293 @@ namespace Atlas::Editor::UI { ImGui::DockSpace(dsID, ImVec2(0.0f, 0.0f), 0); - ImGui::End(); + End(); ImGui::Begin("ResourceTypeSelection", nullptr); ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_Framed; - const char *items[] = {"Audio", "Mesh", "Terrain", "Scene"}; - static int currentSelection = 0; + const char *items[] = {"Audio", "Mesh", "Terrain", "Scene", "Script", "Font"}; for (int i = 0; i < IM_ARRAYSIZE(items); i++) { - ImGui::TreeNodeEx(items[i], nodeFlags); - if (ImGui::IsItemClicked()) - currentSelection = i; + bool isSelected = selectedFilter == i; + ImGui::Selectable(items[i], &isSelected, ImGuiSelectableFlags_SpanAvailWidth); + if (isSelected) { + selectedFilter = i; + } + else if (selectedFilter == i) { + selectedFilter = -1; + } } ImGui::End(); ImGui::Begin("ResourceTypeOverview", nullptr); - switch(currentSelection) { - case 0: - RenderResourceType(IconType::Audio); - break; - case 1: - RenderResourceType(IconType::Mesh); - break; - case 3: - RenderResourceType(IconType::Scene); - default: - break; + RenderDirectoryControl(); + + ImGui::Separator(); + + RenderDirectoryContent(); + + ImGui::End(); + + } + + void ContentBrowserWindow::RenderDirectoryControl() { + + auto assetDirectory = Loader::AssetLoader::GetAssetDirectory(); + + ImGui::SetWindowFontScale(1.5f); + + auto lineHeight = ImGui::GetTextLineHeight(); + auto set = Singletons::imguiWrapper->GetTextureDescriptorSet(Singletons::icons->Get(IconType::ArrowLeft)); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + + if (ImGui::ImageButton(set, ImVec2(lineHeight, lineHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), 0)) { + if (currentDirectory != assetDirectory) { + auto parentPath = std::filesystem::path(currentDirectory).parent_path(); + currentDirectory = parentPath.string(); + } } - End(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + + // Use some less padding due to font scale shift + ImGui::SetCursorPosY(6.0f); + + auto assetPath = Common::Path::GetRelative(assetDirectory, currentDirectory); + assetPath = Common::Path::Normalize(assetPath); + if (assetPath.starts_with('/')) + assetPath.erase(assetPath.begin()); + + assetPath = "//:" + assetPath; + ImGui::Text("%s", assetPath.c_str()); + + ImGui::SameLine(); + + ImGui::SetWindowFontScale(1.0f); + + ImGui::InputTextWithHint("Search", "Type to search for files", &assetSearch); + + } + + void ContentBrowserWindow::RenderDirectoryContent() { + + const float padding = 8.0f; + const float iconSize = 64.f; + + const float itemSize = iconSize + 2.0f * padding; + + float totalWidth = ImGui::GetContentRegionAvail().x; + auto columnItemCount = int32_t(totalWidth / itemSize); + columnItemCount = columnItemCount <= 0 ? 1 : columnItemCount; + + ImGui::Columns(columnItemCount, nullptr, false); + + auto entries = GetFilteredAndSortedDirEntries(); + + auto assetDirectory = Loader::AssetLoader::GetAssetDirectory(); + + std::string nextDirectory; + for (const auto& dirEntry : entries) { + + if (!dirEntry.exists()) + continue; + + auto isDirectory = dirEntry.is_directory(); + + auto path = dirEntry.path().string(); + auto assetPath = Common::Path::GetRelative(assetDirectory, path); + + // Ignore 'invisible' directories + if (isDirectory && assetPath.at(0) == '.') + continue; + + auto filename = Common::Path::GetFileName(path); + + ImVec2 buttonSize = ImVec2(iconSize, iconSize); + + ImGui::BeginGroup(); + + ImGui::PushID(path.c_str()); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + + auto& icon = GetIcon(dirEntry); + auto set = Singletons::imguiWrapper->GetTextureDescriptorSet(icon); + + std::string fileType = Common::Path::GetFileType(path); + std::transform(fileType.begin(), fileType.end(), fileType.begin(), ::tolower); + + // Assign default value, which is not valid if this dir entry is a directory + auto type = FileType::Audio; + if (!dirEntry.is_directory()) + type = FileImporter::fileTypeMapping.at(fileType); + + auto assetRelativePath = Common::Path::GetRelative(assetDirectory, path); + assetRelativePath = Common::Path::Normalize(assetRelativePath); + if (assetRelativePath.starts_with('/')) + assetRelativePath.erase(assetRelativePath.begin()); + + auto buttonFlags = isDirectory || type == FileType::Scene ? ImGuiButtonFlags_PressedOnDoubleClick : 0; + if (ImGui::ImageButtonEx(ImGui::GetID("ImageButton"), set, buttonSize, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), + ImVec4(0.0f, 0.0f, 0.0f, 0.0f), ImVec4(1.0f, 1.0f, 1.0f, 1.0f), buttonFlags)) { + if (isDirectory) + nextDirectory = path; + if (type == FileType::Scene) + FileImporter::ImportFile(assetRelativePath); + } + + if (!isDirectory && ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload("ContentBrowserResource", assetRelativePath.c_str(), assetRelativePath.size()); + ImGui::Text("Drag to entity component"); + + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginPopupContextItem()) { + // We shouldn't allow the user to delete the root entity + if (ImGui::MenuItem("Open externally")) + OpenExternally(std::filesystem::absolute(dirEntry.path()).string(), isDirectory); + + ImGui::EndPopup(); + } + + ImGui::PopStyleColor(); + + auto offset = 0.0f; + + auto textSize = ImGui::CalcTextSize(filename.c_str()); + if (textSize.x < iconSize + padding) + offset = (iconSize + padding - textSize.x) / 2.0f; + + auto cursorX = ImGui::GetCursorPosX(); + ImGui::SetCursorPosX(cursorX + offset); + ImGui::TextWrapped("%s", filename.c_str()); + + ImGui::PopID(); + + ImGui::EndGroup(); + + ImGui::NextColumn(); + + } + + if (!nextDirectory.empty()) { + currentDirectory = nextDirectory; + } + + } + + bool ContentBrowserWindow::IsValidFileType(const std::string& filename) { + + std::string fileType = Common::Path::GetFileType(filename); + std::transform(fileType.begin(), fileType.end(), fileType.begin(), ::tolower); + + return FileImporter::fileTypeMapping.contains(fileType); + + } + + Texture::Texture2D& ContentBrowserWindow::GetIcon(const std::filesystem::directory_entry& dirEntry) { + + auto& icons = Singletons::icons; + + if (dirEntry.is_directory()) + return icons->Get(IconType::Folder); + + auto path = dirEntry.path().string(); + std::string fileType = Common::Path::GetFileType(path); + std::transform(fileType.begin(), fileType.end(), fileType.begin(), ::tolower); + + auto type = FileImporter::fileTypeMapping.at(fileType); + + switch (type) { + case FileType::Audio: return icons->Get(IconType::Audio); + case FileType::Mesh: return icons->Get(IconType::Mesh); + case FileType::Scene: return icons->Get(IconType::Scene); + case FileType::Font: return icons->Get(IconType::Font); + default: return icons->Get(IconType::Document); + } + + } + + std::vector ContentBrowserWindow::GetFilteredAndSortedDirEntries() { + + using dir_entry = std::filesystem::directory_entry; + + FileType filterFileType; + if (selectedFilter >= 0) + filterFileType = static_cast(selectedFilter); + + std::vector entries; + if (assetSearch.empty() && selectedFilter < 0) { + for (const auto& dirEntry : std::filesystem::directory_iterator(currentDirectory)) { + auto path = dirEntry.path().string(); + if (dirEntry.is_directory() || IsValidFileType(path)) + entries.push_back(dirEntry); + } + } + else { + std::string searchQuery = assetSearch; + std::transform(searchQuery.begin(), searchQuery.end(), searchQuery.begin(), ::tolower); + for (const auto& dirEntry : std::filesystem::recursive_directory_iterator(currentDirectory)) { + if (dirEntry.is_directory()) + continue; + + auto path = dirEntry.path().string(); + auto filename = Common::Path::GetFileName(path); + std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower); + + if (!IsValidFileType(path)) + continue; + + // Filter out by file type, if a filter is selected + if (selectedFilter >= 0) { + std::string fileType = Common::Path::GetFileType(filename); + std::transform(fileType.begin(), fileType.end(), fileType.begin(), ::tolower); + + auto type = FileImporter::fileTypeMapping.at(fileType); + if (type != filterFileType) + continue; + } + + // Filter out by search query, if it is a valid (non-empty) query + if (searchQuery.empty() || filename.find(searchQuery) != std::string::npos) + entries.push_back(dirEntry); + } + } + + // Sort for directories to be first and everything to be ordered alphabetically + std::sort(entries.begin(), entries.end(), + [](const dir_entry& entry0, const dir_entry& entry1) { + if (entry0.is_directory() && entry1.is_directory()) { + return entry0.path() < entry1.path(); + } + if (entry0.is_directory()) + return true; + if (entry1.is_directory()) + return false; + + return entry0.path() < entry1.path(); + }); + + return entries; + + } + + void ContentBrowserWindow::OpenExternally(const std::string& path, bool isDirectory) { +#ifdef AE_OS_WINDOWS + ShellExecute(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWDEFAULT); +#endif +#if defined(AE_OS_LINUX) || defined(AE_OS_MACOS) + auto command = "open " + path; + system(command.c_str()); +#endif + } } \ No newline at end of file diff --git a/src/editor/ui/windows/ContentBrowserWindow.h b/src/editor/ui/windows/ContentBrowserWindow.h index 677fa5c47..3cd61ccfe 100644 --- a/src/editor/ui/windows/ContentBrowserWindow.h +++ b/src/editor/ui/windows/ContentBrowserWindow.h @@ -6,8 +6,10 @@ #include "resource/ResourceManager.h" #include "common/Path.h" #include "ImguiExtension/ImguiWrapper.h" +#include "loader/AssetLoader.h" #include +#include namespace Atlas::Editor::UI { @@ -19,69 +21,22 @@ namespace Atlas::Editor::UI { void Render(); private: - template - void RenderResourceType(IconType iconType) { + void RenderDirectoryControl(); - auto resources = ResourceManager::GetResources(); + void RenderDirectoryContent(); - std::sort(resources.begin(), resources.end(), - [=](ResourceHandle resource0, ResourceHandle resource1) -> bool { + bool IsValidFileType(const std::string& filename); - return Common::Path::GetFileName(resource0.GetResource()->path) < - Common::Path::GetFileName(resource1.GetResource()->path); + Texture::Texture2D& GetIcon(const std::filesystem::directory_entry& dirEntry); - }); + std::vector GetFilteredAndSortedDirEntries(); - const float padding = 8.0f; - const float iconSize = 64.f; + void OpenExternally(const std::string& path, bool isDirectory); - const float itemSize = iconSize + 2.0f * padding; + int selectedFilter = -1; - float totalWidth = ImGui::GetContentRegionAvail().x; - auto columnItemCount = int32_t(totalWidth / itemSize); - columnItemCount = std::max(columnItemCount, 1); - - ImGui::Columns(columnItemCount, nullptr, false); - - for (size_t i = 0; i < resources.size(); i++) { - - auto& handle = resources[i]; - auto& resource = handle.GetResource(); - - auto filename = Common::Path::GetFileName(resource->path); - - ImVec2 buttonSize = ImVec2(iconSize, iconSize); - - ImGui::PushID(resource->path.c_str()); - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); - - auto& icon = Singletons::icons->Get(iconType); - auto set = Singletons::imguiWrapper->GetTextureDescriptorSet(icon); - - if (ImGui::ImageButton(set, buttonSize, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), int32_t(padding))) { - - } - - if (ImGui::BeginDragDropSource()) { - auto addr = static_cast(resource.get()); - ImGui::SetDragDropPayload(typeid(T).name(), &addr, sizeof(Resource*)); - ImGui::Text("Drag to entity component"); - - ImGui::EndDragDropSource(); - } - - ImGui::PopStyleColor(); - - ImGui::TextWrapped("%s", filename.c_str()); - - ImGui::PopID(); - - ImGui::NextColumn(); - - } - - } + std::string currentDirectory = Loader::AssetLoader::GetAssetDirectory(); + std::string assetSearch; }; diff --git a/src/editor/ui/windows/LogWindow.cpp b/src/editor/ui/windows/LogWindow.cpp index 5639ea422..ec9ae4eec 100644 --- a/src/editor/ui/windows/LogWindow.cpp +++ b/src/editor/ui/windows/LogWindow.cpp @@ -1,6 +1,7 @@ #include "LogWindow.h" #include "Log.h" +#include "../../Singletons.h" namespace Atlas::Editor::UI { @@ -9,6 +10,8 @@ namespace Atlas::Editor::UI { if (!Begin()) return; + auto darkMode = Singletons::config->darkMode; + static size_t entriesCount = 0; auto entries = Atlas::Log::GetEntries(); @@ -19,7 +22,10 @@ namespace Atlas::Editor::UI { switch (entry.type) { case Atlas::Log::Type::TYPE_MESSAGE: - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + if (darkMode) + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + else + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); break; case Atlas::Log::Type::TYPE_WARNING: ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); diff --git a/src/editor/ui/windows/LogWindow.h b/src/editor/ui/windows/LogWindow.h index 2006ed697..cb150c818 100644 --- a/src/editor/ui/windows/LogWindow.h +++ b/src/editor/ui/windows/LogWindow.h @@ -11,6 +11,9 @@ namespace Atlas::Editor::UI { void Render(); + private: + std::string logSearch; + }; } \ No newline at end of file diff --git a/src/editor/ui/windows/SceneWindow.cpp b/src/editor/ui/windows/SceneWindow.cpp index e76e77c41..079905ef7 100644 --- a/src/editor/ui/windows/SceneWindow.cpp +++ b/src/editor/ui/windows/SceneWindow.cpp @@ -2,6 +2,7 @@ #include "../../Singletons.h" #include "scene/SceneSerializer.h" +#include "Clock.h" #include #include @@ -37,28 +38,13 @@ namespace Atlas::Editor::UI { camera.isMain = true; } - // Temporarily disable all scene cameras, only let editor camera be main - std::map cameraMainMap; - auto cameraSubset = scene->GetSubset(); - for (auto entity : cameraSubset) { - if (entity == cameraEntity || isPlaying) - continue; - - auto& comp = cameraSubset.Get(entity); - cameraMainMap[entity] = comp.isMain; - comp.isMain = false; - } - - scene->Timestep(deltaTime); - scene->Update(); + auto& camera = cameraEntity.GetComponent(); + camera.aspectRatio = float(viewportPanel.viewport->width) / float(viewportPanel.viewport->height); - // Restore all previous camera main values - for (auto entity : cameraSubset) { - if (entity == cameraEntity || isPlaying) - continue; - - auto& comp = cameraSubset.Get(entity); - comp.isMain = cameraMainMap[entity]; + // If we're playing we can update here since we don't expect values to change from the UI side + if (isPlaying) { + scene->Timestep(deltaTime); + scene->Update(); } } @@ -112,6 +98,10 @@ namespace Atlas::Editor::UI { scenePropertiesPanel.Render(scene->irradianceVolume); else if (sceneHierarchyPanel.selectedProperty.reflection) scenePropertiesPanel.Render(scene->reflection); + else if (sceneHierarchyPanel.selectedProperty.ssgi) + scenePropertiesPanel.Render(scene->ssgi); + else if (sceneHierarchyPanel.selectedProperty.sss) + scenePropertiesPanel.Render(scene->sss); else if (sceneHierarchyPanel.selectedProperty.postProcessing) scenePropertiesPanel.Render(scene->postProcessing); else @@ -122,6 +112,33 @@ namespace Atlas::Editor::UI { scenePropertiesPanel.Render(sceneHierarchyPanel.selectedEntity, refScene); } + // We want to update the scene after all panels have update their respective values/changed the scene + if (!isPlaying) { + // Temporarily disable all scene cameras, only let editor camera be main + std::map cameraMainMap; + auto cameraSubset = scene->GetSubset(); + for (auto entity : cameraSubset) { + if (entity == cameraEntity) + continue; + + auto& comp = cameraSubset.Get(entity); + cameraMainMap[entity] = comp.isMain; + comp.isMain = false; + } + + scene->Timestep(Clock::GetDelta()); + scene->Update(); + + // Restore all previous camera main values + for (auto entity : cameraSubset) { + if (entity == cameraEntity) + continue; + + auto& comp = cameraSubset.Get(entity); + comp.isMain = cameraMainMap[entity]; + } + } + RenderEntityBoundingVolumes(sceneHierarchyPanel.selectedEntity); viewportPanel.Render(refScene, inFocus); @@ -134,6 +151,8 @@ namespace Atlas::Editor::UI { viewportPanel.DrawMenuBar([&]() { + const float padding = 8.0f; + auto height = ImGui::GetTextLineHeight(); ImGui::SetCursorPos(ImVec2(0.0f, 0.0f)); @@ -182,7 +201,7 @@ namespace Atlas::Editor::UI { } } - auto offset = region.x / 2.0f - buttonSize.x - 8.0f; + auto offset = region.x / 2.0f - buttonSize.x - padding; ImGui::SetCursorPos(ImVec2(offset, 0.0f)); if (ImGui::ImageButton(set, buttonSize, uvMin, uvMax) && scene.IsLoaded()) { if (hasMainCamera) { @@ -202,7 +221,7 @@ namespace Atlas::Editor::UI { auto& stopIcon = Singletons::icons->Get(IconType::Stop); set = Singletons::imguiWrapper->GetTextureDescriptorSet(stopIcon); - offset = region.x / 2.0f + 8.0f; + offset = region.x / 2.0f + padding; ImGui::SetCursorPos(ImVec2(offset, 0.0f)); if (ImGui::ImageButton(set, buttonSize, uvMin, uvMax) && scene.IsLoaded() && isPlaying) { // Set camera to main in any case @@ -215,6 +234,35 @@ namespace Atlas::Editor::UI { isPlaying = false; } + auto& settingsIcon = Singletons::icons->Get(IconType::Settings); + set = Singletons::imguiWrapper->GetTextureDescriptorSet(settingsIcon); + + uvMin = ImVec2(0.1f, 0.1f); + uvMax = ImVec2(0.9f, 0.9f); + + ImGui::SetCursorPos(ImVec2(region.x - buttonSize.x - padding, 0.0f)); + if (!isPlaying && ImGui::ImageButton(set, buttonSize, uvMin, uvMax) && scene.IsLoaded()) { + ImGui::OpenPopup("Viewport settings"); + } + + if (ImGui::BeginPopup("Viewport settings")) { + ImGui::Text("Movement"); + + ImGui::DragFloat("Movement speed", &cameraMovementSpeed, 0.1f, 0.1f, 100.0f); + ImGui::DragFloat("Rotation speed", &cameraRotationSpeed, 0.1f, 0.1f, 10.0f); + + auto& camera = cameraEntity.GetComponent(); + + ImGui::Text("Editor camera"); + + ImGui::DragFloat("Field of view", &camera.fieldOfView, 0.1f, 1.0f, 180.0f); + + ImGui::DragFloat("Near plane", &camera.nearPlane, 0.01f, 0.01f, 10.0f); + ImGui::DragFloat("Far plane", &camera.farPlane, 1.0f, 1.0f, 20000.0f); + + ImGui::EndPopup(); + } + ImGui::PopStyleColor(); }); @@ -223,7 +271,7 @@ namespace Atlas::Editor::UI { needGuizmoEnabled = false; auto selectedEntity = sceneHierarchyPanel.selectedEntity; - if (cameraEntity.IsValid() && inFocus) { + if (cameraEntity.IsValid() && inFocus && !isPlaying) { needGuizmoEnabled = true; ImGuizmo::SetDrawlist(); @@ -268,6 +316,26 @@ namespace Atlas::Editor::UI { } } + const auto& io = ImGui::GetIO(); + + auto mousePos = vec2(io.MousePos.x, io.MousePos.y); + auto windowPos = ImGui::GetWindowPos(); + + bool inViewport = mousePos.x > float(viewport->x) && mousePos.y > float(viewport->y) + && mousePos.x < float(viewport->width) && mousePos.y < float(viewport->height); + if (io.MouseDown[ImGuiMouseButton_Right] && inViewport) { + + auto nearPoint = viewport->Unproject(vec3(mousePos, 0.0f), camera); + auto farPoint = viewport->Unproject(vec3(mousePos, 1.0f), camera); + + Atlas::Volume::Ray ray(camera.GetLocation(), glm::normalize(farPoint - nearPoint)); + + auto rayCastResult = scene->CastRay(ray); + if (rayCastResult.valid) { + sceneHierarchyPanel.selectedEntity = rayCastResult.data; + } + } + /* const float gridSize = 10.0f; auto gridMatrix = mat4(1.0f); @@ -289,6 +357,18 @@ namespace Atlas::Editor::UI { auto aabb = meshComponent.aabb; viewportPanel.primitiveBatchWrapper.RenderLineAABB(aabb, vec3(1.0f, 1.0f, 0.0f)); } + if (entity.HasComponent()) { + const auto& audioComponent = entity.GetComponent(); + const auto transformComponent = entity.TryGetComponent(); + + vec3 position = vec3(0.0f); + if (transformComponent) + position += transformComponent->Decompose().translation; + + // After this the audio will be cutoff + float radius = powf(audioComponent.falloffFactor / audioComponent.cutoff, 1.0f / audioComponent.falloffPower); + viewportPanel.primitiveBatchWrapper.RenderLineSphere(position, radius, vec3(0.0f, 1.0f, 0.0f)); + } if (entity.HasComponent()) { const auto& audioVolumeComponent = entity.GetComponent(); auto aabb = audioVolumeComponent.GetTransformedAABB(); @@ -306,6 +386,11 @@ namespace Atlas::Editor::UI { Volume::Frustum(component.frustumMatrix), vec3(1.0f, 0.0f, 0.0f)); } } + if (entity.HasComponent()) { + const auto& textComponent = entity.GetComponent(); + auto rectangle = textComponent.GetRectangle(); + viewportPanel.primitiveBatchWrapper.RenderLineRectangle(rectangle,vec3(0.0f, 0.0f, 1.0f)); + } } diff --git a/src/editor/ui/windows/SceneWindow.h b/src/editor/ui/windows/SceneWindow.h index b35208325..e002ed51b 100644 --- a/src/editor/ui/windows/SceneWindow.h +++ b/src/editor/ui/windows/SceneWindow.h @@ -34,6 +34,9 @@ namespace Atlas::Editor::UI { Scene::Entity cameraEntity; + float cameraMovementSpeed = 7.0f; + float cameraRotationSpeed = 1.5f; + // Imguizmo translate mode int32_t guizmoMode = 7; bool needGuizmoEnabled = false; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index d6f2ff4d9..d6e02a5fd 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -73,7 +73,7 @@ namespace Atlas { Physics::PhysicsManager::Init(); // We need lower sample size for smaller buffer on SDL side (e.g. 256 instead of 1024) - Audio::AudioManager::Configure(48000, 2, 256); + Audio::AudioManager::Configure(48000, 2, 128); Clock::Update(); diff --git a/src/engine/Font.cpp b/src/engine/Font.cpp index 06d7a71b1..47b818009 100644 --- a/src/engine/Font.cpp +++ b/src/engine/Font.cpp @@ -11,7 +11,7 @@ namespace Atlas { - Font::Font(std::string filename, float pixelSize, int32_t padding, uint8_t edgeValue) : edgeValue(edgeValue) { + Font::Font(const std::string& filename, float pixelSize, int32_t padding, uint8_t edgeValue) : edgeValue(edgeValue) { stbtt_fontinfo font; diff --git a/src/engine/Font.h b/src/engine/Font.h index 50004db10..11a62c074 100644 --- a/src/engine/Font.h +++ b/src/engine/Font.h @@ -57,7 +57,7 @@ namespace Atlas { * because 5 - 5 * 100/255 is approximately 3. Everything outside this area can be used * as the outline. */ - Font(std::string filename, float pixelSize, int32_t padding, uint8_t edgeValue = 127); + Font(const std::string& filename, float pixelSize, int32_t padding, uint8_t edgeValue = 127); /** * Returns the glyph of a character diff --git a/src/engine/Viewport.cpp b/src/engine/Viewport.cpp index 31ba6ee6c..561ebbd4c 100644 --- a/src/engine/Viewport.cpp +++ b/src/engine/Viewport.cpp @@ -49,17 +49,17 @@ namespace Atlas { vec3 Viewport::Unproject(vec3 point, const CameraComponent& camera) const { - float fWidth = float(width) + float(x); - float fHeight = float(height) + float(y); + float fWidth = float(width); + float fHeight = float(height); float fX = float(x); float fY = float(y); - point.x = glm::clamp(point.x, fX, fWidth); - point.y = glm::clamp(point.y, fY, fHeight); + point.x = glm::clamp(point.x - fX, 0.0f, fWidth); + point.y = glm::clamp(point.y - fY, 0.0f, fHeight); point.z = glm::clamp(2.0f * point.z - 1.0f, -1.0f, 1.0f); - point.x = 2.0f * (point.x - fX) / (fWidth - fX) - 1.0f; - point.y = -2.0f * (point.y - fY) / (fHeight - fY) + 1.0f; + point.x = 2.0f * point.x / fWidth - 1.0f; + point.y = 2.0f * point.y / fHeight - 1.0f; vec4 transformed = camera.invViewMatrix * camera.invUnjitteredProjection * vec4(point, 1.0f); diff --git a/src/engine/audio/AudioSerializer.cpp b/src/engine/audio/AudioSerializer.cpp new file mode 100644 index 000000000..00c28272e --- /dev/null +++ b/src/engine/audio/AudioSerializer.cpp @@ -0,0 +1,41 @@ +#include "AudioSerializer.h" + +namespace Atlas::Audio { + + void to_json(json& j, const AudioStream& p) { + + j = json{ + {"time", p.GetTime()}, + {"pitch", p.GetPitch()}, + {"volume", p.GetVolume()}, + {"loop", p.loop}, + }; + + if (p.data.IsValid()) + j["resourcePath"] = p.data.GetResource()->path; + + } + + void from_json(const json& j, AudioStream& p) { + + double time, pitch; + float volume; + + j.at("time").get_to(time); + j.at("pitch").get_to(pitch); + j.at("volume").get_to(volume); + j.at("loop").get_to(p.loop); + + p.SetTime(time); + p.SetPitch(pitch); + p.SetVolume(volume); + + if (j.contains("resourcePath")) { + std::string resourcePath = j["resourcePath"]; + auto handle = ResourceManager::GetOrLoadResourceAsync(resourcePath); + p.ChangeData(handle); + } + + } + +} \ No newline at end of file diff --git a/src/engine/audio/AudioSerializer.h b/src/engine/audio/AudioSerializer.h index b24a9843f..ebedde75d 100644 --- a/src/engine/audio/AudioSerializer.h +++ b/src/engine/audio/AudioSerializer.h @@ -7,40 +7,8 @@ namespace Atlas::Audio { - void to_json(json& j, const AudioStream& p) { + void to_json(json& j, const AudioStream& p); - j = json{ - {"time", p.GetTime()}, - {"pitch", p.GetPitch()}, - {"volume", p.GetVolume()}, - {"loop", p.loop}, - }; - - if (p.data.IsValid()) - j["resourcePath"] = p.data.GetResource()->path; - - } - - void from_json(const json& j, AudioStream& p) { - - double time, pitch; - float volume; - - j.at("time").get_to(time); - j.at("pitch").get_to(pitch); - j.at("volume").get_to(volume); - j.at("loop").get_to(p.loop); - - p.SetTime(time); - p.SetPitch(pitch); - p.SetVolume(volume); - - if (j.contains("resourcePath")) { - std::string resourcePath = j["resourcePath"]; - auto handle = ResourceManager::GetOrLoadResourceAsync(resourcePath); - p.ChangeData(handle); - } - - } + void from_json(const json& j, AudioStream& p); } \ No newline at end of file diff --git a/src/engine/audio/AudioStream.cpp b/src/engine/audio/AudioStream.cpp index 31e5735ee..865425974 100644 --- a/src/engine/audio/AudioStream.cpp +++ b/src/engine/audio/AudioStream.cpp @@ -30,7 +30,7 @@ namespace Atlas { std::lock_guard lock(mutex); - if (!data.IsValid()) + if (!data.IsLoaded()) return; progress = time * (double)(data->GetFrequency() * data->GetSampleSize() / data->GetChannelCount()) / 2.0; @@ -42,7 +42,7 @@ namespace Atlas { std::lock_guard lock(mutex); - if (!data.IsValid()) + if (!data.IsLoaded()) return 0.0; return 2.0 * progress * (double)data->GetChannelCount() / diff --git a/src/engine/common/Path.cpp b/src/engine/common/Path.cpp index 9889a9069..b837bd454 100644 --- a/src/engine/common/Path.cpp +++ b/src/engine/common/Path.cpp @@ -16,6 +16,9 @@ namespace Atlas { std::string Path::GetRelative(std::string src, std::string dest) { + if (src == dest) + return ""; + std::string result; src = GetAbsolute(src); diff --git a/src/engine/common/SerializationHelper.cpp b/src/engine/common/SerializationHelper.cpp index 06d1ef303..621f9f134 100644 --- a/src/engine/common/SerializationHelper.cpp +++ b/src/engine/common/SerializationHelper.cpp @@ -74,6 +74,20 @@ namespace glm { j.at("w").get_to(p.w); } + void to_json(json &j, const quat &p) { + j = json{{"x", p.x}, + {"y", p.y}, + {"z", p.z}, + {"w", p.w}}; + } + + void from_json(const json &j, quat &p) { + j.at("x").get_to(p.x); + j.at("y").get_to(p.y); + j.at("z").get_to(p.z); + j.at("w").get_to(p.w); + } + void to_json(json &j, const mat3 &p) { json j0, j1, j2; to_json(j0, p[0]); diff --git a/src/engine/common/SerializationHelper.h b/src/engine/common/SerializationHelper.h index e2e86eaf0..6509be68e 100644 --- a/src/engine/common/SerializationHelper.h +++ b/src/engine/common/SerializationHelper.h @@ -33,6 +33,10 @@ namespace glm { void from_json(const json& j, vec4& p); + void to_json(json& j, const quat& p); + + void from_json(const json& j, quat& p); + void to_json(json& j, const mat3& p); void from_json(const json& j, mat3& p); diff --git a/src/engine/input/Keyboard.cpp b/src/engine/input/Keyboard.cpp index 0f9ad59b9..5e187dd75 100644 --- a/src/engine/input/Keyboard.cpp +++ b/src/engine/input/Keyboard.cpp @@ -159,7 +159,7 @@ namespace Atlas { if (event.keyCode == AE_KEY_LSHIFT && event.state == AE_BUTTON_RELEASED) { fast = false; } - + } diff --git a/src/engine/lighting/LightingSerializer.cpp b/src/engine/lighting/LightingSerializer.cpp new file mode 100644 index 000000000..fb7ecce97 --- /dev/null +++ b/src/engine/lighting/LightingSerializer.cpp @@ -0,0 +1,363 @@ +#include "LightingSerializer.h" + +namespace Atlas::Lighting { + + void to_json(json& j, const AO& p) { + j = json { + {"sampleCount", p.sampleCount}, + {"radius", p.radius}, + {"strength", p.strength}, + {"enable", p.enable}, + {"rt", p.rt}, + {"opacityCheck", p.opacityCheck}, + }; + } + + void from_json(const json& j, AO& p) { + p = AO(j["sampleCount"].get()); + j.at("radius").get_to(p.radius); + j.at("strength").get_to(p.strength); + j.at("enable").get_to(p.enable); + j.at("rt").get_to(p.rt); + j.at("opacityCheck").get_to(p.opacityCheck); + } + + void to_json(json& j, const EnvironmentProbe& p) { + j = json { + {"resolution", p.resolution}, + {"position", p.GetPosition()} + }; + } + + void from_json(const json& j, EnvironmentProbe& p) { + p = EnvironmentProbe(j["resolution"].get(), + j["position"].get()); + } + + void to_json(json& j, const Atmosphere& p) { + j = json { + {"height", p.height}, + {"probeResolution", p.probe->resolution} + }; + } + + void from_json(const json& j, Atmosphere& p) { + p = Atmosphere(j["height"].get(), + j["probeResolution"].get()); + } + + void to_json(json& j, const Fog& p) { + j = json { + {"enable", p.enable}, + {"scatteringFactor", p.scatteringFactor}, + {"extinctionFactor", p.extinctionFactor}, + {"extinctionCoefficients", p.extinctionCoefficients}, + {"ambientFactor", p.ambientFactor}, + {"density", p.density}, + {"height", p.height}, + {"heightFalloff", p.heightFalloff}, + {"scatteringAnisotropy", p.scatteringAnisotropy}, + {"rayMarching", p.rayMarching}, + {"rayMarchStepCount", p.rayMarchStepCount}, + {"volumetricIntensity", p.volumetricIntensity}, + }; + } + + void from_json(const json& j, Fog& p) { + j.at("enable").get_to(p.enable); + j.at("scatteringFactor").get_to(p.scatteringFactor); + j.at("extinctionFactor").get_to(p.extinctionFactor); + j.at("extinctionCoefficients").get_to(p.extinctionCoefficients); + j.at("ambientFactor").get_to(p.ambientFactor); + j.at("density").get_to(p.density); + j.at("height").get_to(p.height); + j.at("heightFalloff").get_to(p.heightFalloff); + j.at("scatteringAnisotropy").get_to(p.scatteringAnisotropy); + j.at("rayMarching").get_to(p.rayMarching); + j.at("rayMarchStepCount").get_to(p.rayMarchStepCount); + j.at("volumetricIntensity").get_to(p.volumetricIntensity); + } + + void to_json(json& j, const IrradianceVolume& p) { + j = json { + {"aabb", p.aabb}, + {"probeCount", p.probeCount}, + {"lowerResMoments", p.lowerResMoments}, + {"enable", p.enable}, + {"rayCount", p.rayCount}, + {"rayCountInactive", p.rayCountInactive}, + {"hysteresis", p.hysteresis}, + {"bias", p.bias}, + {"sharpness", p.sharpness}, + {"gamma", p.gamma}, + {"strength", p.strength}, + {"sampleEmissives", p.sampleEmissives}, + {"optimizeProbes", p.optimizeProbes}, + {"useShadowMap", p.useShadowMap}, + {"opacityCheck", p.opacityCheck}, + }; + } + + void from_json(const json& j, IrradianceVolume& p) { + p = IrradianceVolume(j["aabb"].get(), + j["probeCount"].get(), j["lowerResMoments"].get()); + j.at("enable").get_to(p.enable); + j.at("rayCount").get_to(p.rayCount); + j.at("rayCountInactive").get_to(p.rayCountInactive); + j.at("hysteresis").get_to(p.hysteresis); + j.at("bias").get_to(p.bias); + j.at("sharpness").get_to(p.sharpness); + j.at("gamma").get_to(p.gamma); + j.at("strength").get_to(p.strength); + j.at("sampleEmissives").get_to(p.sampleEmissives); + j.at("optimizeProbes").get_to(p.optimizeProbes); + j.at("useShadowMap").get_to(p.useShadowMap); + j.at("opacityCheck").get_to(p.opacityCheck); + } + + void to_json(json& j, const Reflection& p) { + j = json { + {"textureLevel", p.textureLevel}, + {"radianceLimit", p.radianceLimit}, + {"bias", p.bias}, + {"spatialFilterStrength", p.spatialFilterStrength}, + {"temporalWeight", p.temporalWeight}, + {"historyClipMax", p.historyClipMax}, + {"currentClipFactor", p.currentClipFactor}, + {"enable", p.enable}, + {"rt", p.rt}, + {"gi", p.gi}, + {"useShadowMap", p.useShadowMap}, + {"opacityCheck", p.opacityCheck}, + }; + } + + void from_json(const json& j, Reflection& p) { + j.at("textureLevel").get_to(p.textureLevel); + j.at("radianceLimit").get_to(p.radianceLimit); + j.at("bias").get_to(p.bias); + j.at("spatialFilterStrength").get_to(p.spatialFilterStrength); + j.at("temporalWeight").get_to(p.temporalWeight); + j.at("historyClipMax").get_to(p.historyClipMax); + j.at("currentClipFactor").get_to(p.currentClipFactor); + j.at("enable").get_to(p.enable); + j.at("rt").get_to(p.rt); + j.at("gi").get_to(p.gi); + j.at("useShadowMap").get_to(p.useShadowMap); + j.at("opacityCheck").get_to(p.opacityCheck); + } + + void to_json(json& j, const ShadowView& p) { + j = json { + {"nearDistance", p.nearDistance}, + {"farDistance", p.farDistance}, + {"viewMatrix", p.viewMatrix}, + {"projectionMatrix", p.projectionMatrix}, + {"frustumMatrix", p.frustumMatrix}, + {"terrainFrustumMatrix", p.terrainFrustumMatrix}, + {"orthoSize", p.orthoSize}, + }; + } + + void from_json(const json& j, ShadowView& p) { + j.at("nearDistance").get_to(p.nearDistance); + j.at("farDistance").get_to(p.farDistance); + j.at("viewMatrix").get_to(p.viewMatrix); + j.at("projectionMatrix").get_to(p.projectionMatrix); + j.at("frustumMatrix").get_to(p.frustumMatrix); + j.at("terrainFrustumMatrix").get_to(p.terrainFrustumMatrix); + j.at("orthoSize").get_to(p.orthoSize); + } + + void to_json(json& j, const Shadow& p) { + j = json { + {"center", p.center}, + {"distance", p.distance}, + {"longRangeDistance", p.longRangeDistance}, + {"bias", p.bias}, + {"splitCorrection", p.splitCorrection}, + {"cascadeBlendDistance", p.cascadeBlendDistance}, + {"resolution", p.resolution}, + {"views", p.views}, + {"viewCount", p.viewCount}, + {"isCascaded", p.isCascaded}, + {"followMainCamera", p.followMainCamera}, + {"useCubemap", p.useCubemap}, + {"allowDynamicEntities", p.allowDynamicEntities}, + {"allowTerrain", p.allowTerrain}, + {"longRange", p.longRange} + }; + } + + void from_json(const json& j, Shadow& p) { + j.at("center").get_to(p.center); + j.at("distance").get_to(p.distance); + j.at("longRangeDistance").get_to(p.longRangeDistance); + j.at("bias").get_to(p.bias); + j.at("splitCorrection").get_to(p.splitCorrection); + j.at("cascadeBlendDistance").get_to(p.cascadeBlendDistance); + j.at("resolution").get_to(p.resolution); + j.at("views").get_to(p.views); + j.at("viewCount").get_to(p.viewCount); + j.at("isCascaded").get_to(p.isCascaded); + j.at("followMainCamera").get_to(p.followMainCamera); + j.at("useCubemap").get_to(p.useCubemap); + j.at("allowDynamicEntities").get_to(p.allowDynamicEntities); + j.at("allowTerrain").get_to(p.allowTerrain); + j.at("longRange").get_to(p.longRange); + + p.SetResolution(p.resolution); + } + + void to_json(json& j, const SSGI& p) { + j = json { + {"enable", p.enable}, + {"enableAo", p.enableAo}, + {"radius", p.radius}, + {"rayCount", p.rayCount}, + {"sampleCount", p.sampleCount}, + {"irradianceLimit", p.irradianceLimit}, + {"aoStrength", p.aoStrength}, + {"rt", p.rt}, + {"opacityCheck", p.opacityCheck}, + }; + } + + void from_json(const json& j, SSGI& p) { + j.at("enable").get_to(p.enable); + j.at("enableAo").get_to(p.enableAo); + j.at("radius").get_to(p.radius); + j.at("rayCount").get_to(p.rayCount); + j.at("sampleCount").get_to(p.sampleCount); + j.at("irradianceLimit").get_to(p.irradianceLimit); + j.at("aoStrength").get_to(p.aoStrength); + j.at("rt").get_to(p.rt); + j.at("opacityCheck").get_to(p.opacityCheck); + } + + void to_json(json& j, const SSS& p) { + j = json { + {"sampleCount", p.sampleCount}, + {"maxLength", p.maxLength}, + {"thickness", p.thickness}, + {"enable", p.enable}, + }; + } + + void from_json(const json& j, SSS& p) { + j.at("sampleCount").get_to(p.sampleCount); + j.at("maxLength").get_to(p.maxLength); + j.at("thickness").get_to(p.thickness); + j.at("enable").get_to(p.enable); + } + + void to_json(json& j, const VolumetricClouds::Scattering& p) { + j = json { + {"extinctionFactor", p.extinctionFactor}, + {"scatteringFactor", p.scatteringFactor}, + {"extinctionCoefficients", p.extinctionCoefficients}, + {"eccentricityFirstPhase", p.eccentricityFirstPhase}, + {"eccentricitySecondPhase", p.eccentricitySecondPhase}, + {"phaseAlpha", p.phaseAlpha}, + }; + } + + void from_json(const json& j, VolumetricClouds::Scattering& p) { + j.at("extinctionFactor").get_to(p.extinctionFactor); + j.at("scatteringFactor").get_to(p.scatteringFactor); + j.at("extinctionCoefficients").get_to(p.extinctionCoefficients); + j.at("eccentricityFirstPhase").get_to(p.eccentricityFirstPhase); + j.at("eccentricitySecondPhase").get_to(p.eccentricitySecondPhase); + j.at("phaseAlpha").get_to(p.phaseAlpha); + } + + void to_json(json& j, const VolumetricClouds& p) { + j = json { + {"coverageResolution", p.coverageTexture.width}, + {"shapeResolution", p.shapeTexture.width}, + {"detailResolution", p.detailTexture.width}, + {"shadowResolution", p.shadowTexture.width}, + {"sampleCount", p.sampleCount}, + {"occlusionSampleCount", p.occlusionSampleCount}, + {"shadowSampleFraction", p.shadowSampleFraction}, + {"minHeight", p.minHeight}, + {"maxHeight", p.maxHeight}, + {"distanceLimit", p.distanceLimit}, + {"coverageScale", p.coverageScale}, + {"shapeScale", p.shapeScale}, + {"detailScale", p.detailScale}, + {"coverageSpeed", p.coverageSpeed}, + {"shapeSpeed", p.shapeSpeed}, + {"detailSpeed", p.detailSpeed}, + {"detailStrength", p.detailStrength}, + {"densityMultiplier", p.densityMultiplier}, + {"heightStretch", p.heightStretch}, + {"darkEdgeFocus", p.darkEdgeFocus}, + {"darkEdgeAmbient", p.darkEdgeAmbient}, + {"scattering", p.scattering}, + {"enable", p.enable}, + {"castShadow", p.castShadow}, + {"stochasticOcclusionSampling", p.stochasticOcclusionSampling}, + }; + } + + void from_json(const json& j, VolumetricClouds& p) { + p = VolumetricClouds(j["coverageResolution"].get(), j["shapeResolution"].get(), + j["detailResolution"].get(), j["shadowResolution"].get()); + + j.at("sampleCount").get_to(p.sampleCount); + j.at("occlusionSampleCount").get_to(p.occlusionSampleCount); + j.at("shadowSampleFraction").get_to(p.shadowSampleFraction); + j.at("minHeight").get_to(p.minHeight); + j.at("maxHeight").get_to(p.maxHeight); + j.at("distanceLimit").get_to(p.distanceLimit); + j.at("coverageScale").get_to(p.coverageScale); + j.at("shapeScale").get_to(p.shapeScale); + j.at("detailScale").get_to(p.detailScale); + j.at("coverageSpeed").get_to(p.coverageSpeed); + j.at("shapeSpeed").get_to(p.shapeSpeed); + j.at("detailSpeed").get_to(p.detailSpeed); + j.at("detailStrength").get_to(p.detailStrength); + j.at("densityMultiplier").get_to(p.densityMultiplier); + j.at("heightStretch").get_to(p.heightStretch); + j.at("darkEdgeFocus").get_to(p.darkEdgeFocus); + j.at("darkEdgeAmbient").get_to(p.darkEdgeAmbient); + j.at("scattering").get_to(p.scattering); + j.at("enable").get_to(p.enable); + j.at("castShadow").get_to(p.castShadow); + j.at("stochasticOcclusionSampling").get_to(p.stochasticOcclusionSampling); + } + + void to_json(json& j, const Sky& p) { + j = json { + {"planetCenter", p.planetCenter}, + {"planetRadius", p.planetRadius} + }; + + if (p.atmosphere) + j["atmosphere"] = *p.atmosphere; + if (p.clouds) + j["clouds"] = *p.clouds; + if (p.probe) + j["probe"] = *p.probe; + } + + void from_json(const json& j, Sky& p) { + j.at("planetCenter").get_to(p.planetCenter); + j.at("planetRadius").get_to(p.planetRadius); + + if (j.contains("atmosphere")) { + p.atmosphere = CreateRef(); + *p.atmosphere = j["atmosphere"]; + } + if (j.contains("clouds")) { + p.clouds = CreateRef(); + *p.clouds = j["clouds"]; + } + if (j.contains("probe")) { + p.probe = CreateRef(); + *p.probe = j["probe"]; + } + } + +} \ No newline at end of file diff --git a/src/engine/lighting/LightingSerializer.h b/src/engine/lighting/LightingSerializer.h index af3752295..b21043ac9 100644 --- a/src/engine/lighting/LightingSerializer.h +++ b/src/engine/lighting/LightingSerializer.h @@ -3,361 +3,69 @@ #include "common/SerializationHelper.h" #include "Shadow.h" +#include "AO.h" +#include "SSGI.h" +#include "EnvironmentProbe.h" +#include "SSS.h" +#include "IrradianceVolume.h" +#include "Atmosphere.h" +#include "Reflection.h" +#include "Fog.h" +#include "VolumetricClouds.h" +#include "Sky.h" namespace Atlas::Lighting { - void to_json(json& j, const AO& p) { - j = json { - {"sampleCount", p.sampleCount}, - {"radius", p.radius}, - {"strength", p.strength}, - {"enable", p.enable}, - {"rt", p.rt}, - {"opacityCheck", p.opacityCheck}, - }; - } + void to_json(json& j, const AO& p); - void from_json(const json& j, AO& p) { - p = AO(j["sampleCount"].get()); - j.at("radius").get_to(p.radius); - j.at("strength").get_to(p.strength); - j.at("enable").get_to(p.enable); - j.at("rt").get_to(p.rt); - j.at("opacityCheck").get_to(p.opacityCheck); - } + void from_json(const json& j, AO& p); - void to_json(json& j, const EnvironmentProbe& p) { - j = json { - {"resolution", p.resolution}, - {"position", p.GetPosition()} - }; - } + void to_json(json& j, const EnvironmentProbe& p); - void from_json(const json& j, EnvironmentProbe& p) { - p = EnvironmentProbe(j["resolution"].get(), - j["position"].get()); - } + void from_json(const json& j, EnvironmentProbe& p); - void to_json(json& j, const Atmosphere& p) { - j = json { - {"height", p.height}, - {"probeResolution", p.probe->resolution} - }; - } + void to_json(json& j, const Atmosphere& p); - void from_json(const json& j, Atmosphere& p) { - p = Atmosphere(j["height"].get(), - j["probeResolution"].get()); - } + void from_json(const json& j, Atmosphere& p); - void to_json(json& j, const Fog& p) { - j = json { - {"enable", p.enable}, - {"scatteringFactor", p.scatteringFactor}, - {"extinctionFactor", p.extinctionFactor}, - {"extinctionCoefficients", p.extinctionCoefficients}, - {"ambientFactor", p.ambientFactor}, - {"density", p.density}, - {"height", p.height}, - {"heightFalloff", p.heightFalloff}, - {"scatteringAnisotropy", p.scatteringAnisotropy}, - {"rayMarching", p.rayMarching}, - {"rayMarchStepCount", p.rayMarchStepCount}, - {"volumetricIntensity", p.volumetricIntensity}, - }; - } + void to_json(json& j, const Fog& p); - void from_json(const json& j, Fog& p) { - j.at("enable").get_to(p.enable); - j.at("scatteringFactor").get_to(p.scatteringFactor); - j.at("extinctionFactor").get_to(p.extinctionFactor); - j.at("extinctionCoefficients").get_to(p.extinctionCoefficients); - j.at("ambientFactor").get_to(p.ambientFactor); - j.at("density").get_to(p.density); - j.at("height").get_to(p.height); - j.at("heightFalloff").get_to(p.heightFalloff); - j.at("scatteringAnisotropy").get_to(p.scatteringAnisotropy); - j.at("rayMarching").get_to(p.rayMarching); - j.at("rayMarchStepCount").get_to(p.rayMarchStepCount); - j.at("volumetricIntensity").get_to(p.volumetricIntensity); - } + void from_json(const json& j, Fog& p); - void to_json(json& j, const IrradianceVolume& p) { - j = json { - {"aabb", p.aabb}, - {"probeCount", p.probeCount}, - {"lowerResMoments", p.lowerResMoments}, - {"enable", p.enable}, - {"rayCount", p.rayCount}, - {"rayCountInactive", p.rayCountInactive}, - {"hysteresis", p.hysteresis}, - {"bias", p.bias}, - {"sharpness", p.sharpness}, - {"gamma", p.gamma}, - {"strength", p.strength}, - {"sampleEmissives", p.sampleEmissives}, - {"optimizeProbes", p.optimizeProbes}, - {"useShadowMap", p.useShadowMap}, - {"opacityCheck", p.opacityCheck}, - }; - } + void to_json(json& j, const IrradianceVolume& p); - void from_json(const json& j, IrradianceVolume& p) { - p = IrradianceVolume(j["aabb"].get(), - j["probeCount"].get(), j["lowerResMoments"].get()); - j.at("enable").get_to(p.enable); - j.at("rayCount").get_to(p.rayCount); - j.at("rayCountInactive").get_to(p.rayCountInactive); - j.at("hysteresis").get_to(p.hysteresis); - j.at("bias").get_to(p.bias); - j.at("sharpness").get_to(p.sharpness); - j.at("gamma").get_to(p.gamma); - j.at("strength").get_to(p.strength); - j.at("sampleEmissives").get_to(p.sampleEmissives); - j.at("optimizeProbes").get_to(p.optimizeProbes); - j.at("useShadowMap").get_to(p.useShadowMap); - j.at("opacityCheck").get_to(p.opacityCheck); - } + void from_json(const json& j, IrradianceVolume& p); - void to_json(json& j, const Reflection& p) { - j = json { - {"textureLevel", p.textureLevel}, - {"radianceLimit", p.radianceLimit}, - {"bias", p.bias}, - {"spatialFilterStrength", p.spatialFilterStrength}, - {"temporalWeight", p.temporalWeight}, - {"historyClipMax", p.historyClipMax}, - {"currentClipFactor", p.currentClipFactor}, - {"enable", p.enable}, - {"rt", p.rt}, - {"gi", p.gi}, - {"useShadowMap", p.useShadowMap}, - {"opacityCheck", p.opacityCheck}, - }; - } + void to_json(json& j, const Reflection& p); - void from_json(const json& j, Reflection& p) { - j.at("textureLevel").get_to(p.textureLevel); - j.at("radianceLimit").get_to(p.radianceLimit); - j.at("bias").get_to(p.bias); - j.at("spatialFilterStrength").get_to(p.spatialFilterStrength); - j.at("temporalWeight").get_to(p.temporalWeight); - j.at("historyClipMax").get_to(p.historyClipMax); - j.at("currentClipFactor").get_to(p.currentClipFactor); - j.at("enable").get_to(p.enable); - j.at("rt").get_to(p.rt); - j.at("gi").get_to(p.gi); - j.at("useShadowMap").get_to(p.useShadowMap); - j.at("opacityCheck").get_to(p.opacityCheck); - } + void from_json(const json& j, Reflection& p); - void to_json(json& j, const ShadowView& p) { - j = json { - {"nearDistance", p.nearDistance}, - {"farDistance", p.farDistance}, - {"viewMatrix", p.viewMatrix}, - {"projectionMatrix", p.projectionMatrix}, - {"frustumMatrix", p.frustumMatrix}, - {"terrainFrustumMatrix", p.terrainFrustumMatrix}, - }; - } + void to_json(json& j, const ShadowView& p); - void from_json(const json& j, ShadowView& p) { - j.at("nearDistance").get_to(p.nearDistance); - j.at("farDistance").get_to(p.farDistance); - j.at("viewMatrix").get_to(p.viewMatrix); - j.at("projectionMatrix").get_to(p.projectionMatrix); - j.at("frustumMatrix").get_to(p.frustumMatrix); - j.at("terrainFrustumMatrix").get_to(p.terrainFrustumMatrix); - } + void from_json(const json& j, ShadowView& p); - void to_json(json& j, const Shadow& p) { - j = json { - {"center", p.center}, - {"distance", p.distance}, - {"longRangeDistance", p.longRangeDistance}, - {"bias", p.bias}, - {"splitCorrection", p.splitCorrection}, - {"cascadeBlendDistance", p.cascadeBlendDistance}, - {"resolution", p.resolution}, - {"views", p.views}, - {"viewCount", p.viewCount}, - {"isCascaded", p.isCascaded}, - {"useCubemap", p.useCubemap}, - {"allowDynamicActors", p.allowDynamicActors}, - {"allowTerrain", p.allowTerrain}, - {"longRange", p.longRange} - }; - } + void to_json(json& j, const Shadow& p); - void from_json(const json& j, Shadow& p) { - j.at("center").get_to(p.center); - j.at("distance").get_to(p.distance); - j.at("longRangeDistance").get_to(p.longRangeDistance); - j.at("bias").get_to(p.bias); - j.at("splitCorrection").get_to(p.splitCorrection); - j.at("cascadeBlendDistance").get_to(p.cascadeBlendDistance); - j.at("resolution").get_to(p.resolution); - j.at("views").get_to(p.views); - j.at("viewCount").get_to(p.viewCount); - j.at("isCascaded").get_to(p.isCascaded); - j.at("useCubemap").get_to(p.useCubemap); - j.at("allowDynamicActors").get_to(p.allowDynamicActors); - j.at("allowTerrain").get_to(p.allowTerrain); - j.at("longRange").get_to(p.longRange); + void from_json(const json& j, Shadow& p); - p.SetResolution(p.resolution); - } + void to_json(json& j, const SSGI& p); - void to_json(json& j, const SSGI& p) { - j = json { - {"enable", p.enable}, - {"enableAo", p.enableAo}, - {"radius", p.radius}, - {"rayCount", p.rayCount}, - {"sampleCount", p.sampleCount}, - {"irradianceLimit", p.irradianceLimit}, - {"aoStrength", p.aoStrength}, - {"rt", p.rt}, - {"opacityCheck", p.opacityCheck}, - }; - } + void from_json(const json& j, SSGI& p); - void from_json(const json& j, SSGI& p) { - j.at("enable").get_to(p.enable); - j.at("enableAo").get_to(p.enableAo); - j.at("radius").get_to(p.radius); - j.at("rayCount").get_to(p.rayCount); - j.at("sampleCount").get_to(p.sampleCount); - j.at("irradianceLimit").get_to(p.irradianceLimit); - j.at("aoStrength").get_to(p.aoStrength); - j.at("rt").get_to(p.rt); - j.at("opacityCheck").get_to(p.opacityCheck); - } + void to_json(json& j, const SSS& p); - void to_json(json& j, const SSS& p) { - j = json { - {"sampleCount", p.sampleCount}, - {"maxLength", p.maxLength}, - {"thickness", p.thickness}, - {"enable", p.enable}, - }; - } + void from_json(const json& j, SSS& p); - void from_json(const json& j, SSS& p) { - j.at("sampleCount").get_to(p.sampleCount); - j.at("maxLength").get_to(p.maxLength); - j.at("thickness").get_to(p.thickness); - j.at("enable").get_to(p.enable); - } + void to_json(json& j, const VolumetricClouds::Scattering& p); - void to_json(json& j, const VolumetricClouds::Scattering& p) { - j = json { - {"extinctionFactor", p.extinctionFactor}, - {"scatteringFactor", p.scatteringFactor}, - {"extinctionCoefficients", p.extinctionCoefficients}, - {"eccentricityFirstPhase", p.eccentricityFirstPhase}, - {"eccentricitySecondPhase", p.eccentricitySecondPhase}, - {"phaseAlpha", p.phaseAlpha}, - }; - } + void from_json(const json& j, VolumetricClouds::Scattering& p); - void from_json(const json& j, VolumetricClouds::Scattering& p) { - j.at("extinctionFactor").get_to(p.extinctionFactor); - j.at("scatteringFactor").get_to(p.scatteringFactor); - j.at("extinctionCoefficients").get_to(p.extinctionCoefficients); - j.at("eccentricityFirstPhase").get_to(p.eccentricityFirstPhase); - j.at("eccentricitySecondPhase").get_to(p.eccentricitySecondPhase); - j.at("phaseAlpha").get_to(p.phaseAlpha); - } + void to_json(json& j, const VolumetricClouds& p); - void to_json(json& j, const VolumetricClouds& p) { - j = json { - {"coverageResolution", p.coverageTexture.width}, - {"shapeResolution", p.shapeTexture.width}, - {"detailResolution", p.detailTexture.width}, - {"shadowResolution", p.shadowTexture.width}, - {"sampleCount", p.sampleCount}, - {"occlusionSampleCount", p.occlusionSampleCount}, - {"shadowSampleFraction", p.shadowSampleFraction}, - {"minHeight", p.minHeight}, - {"maxHeight", p.maxHeight}, - {"distanceLimit", p.distanceLimit}, - {"coverageScale", p.coverageScale}, - {"shapeScale", p.shapeScale}, - {"detailScale", p.detailScale}, - {"coverageSpeed", p.coverageSpeed}, - {"shapeSpeed", p.shapeSpeed}, - {"detailSpeed", p.detailSpeed}, - {"detailStrength", p.detailStrength}, - {"densityMultiplier", p.densityMultiplier}, - {"heightStretch", p.heightStretch}, - {"darkEdgeFocus", p.darkEdgeFocus}, - {"darkEdgeAmbient", p.darkEdgeAmbient}, - {"scattering", p.scattering}, - {"enable", p.enable}, - {"castShadow", p.castShadow}, - {"stochasticOcclusionSampling", p.stochasticOcclusionSampling}, - }; - } + void from_json(const json& j, VolumetricClouds& p); - void from_json(const json& j, VolumetricClouds& p) { - p = VolumetricClouds(j["coverageResolution"].get(), j["shapeResolution"].get(), - j["detailResolution"].get(), j["shadowResolution"].get()); + void to_json(json& j, const Sky& p); - j.at("sampleCount").get_to(p.sampleCount); - j.at("occlusionSampleCount").get_to(p.occlusionSampleCount); - j.at("shadowSampleFraction").get_to(p.shadowSampleFraction); - j.at("minHeight").get_to(p.minHeight); - j.at("maxHeight").get_to(p.maxHeight); - j.at("distanceLimit").get_to(p.distanceLimit); - j.at("coverageScale").get_to(p.coverageScale); - j.at("shapeScale").get_to(p.shapeScale); - j.at("detailScale").get_to(p.detailScale); - j.at("coverageSpeed").get_to(p.coverageSpeed); - j.at("shapeSpeed").get_to(p.shapeSpeed); - j.at("detailSpeed").get_to(p.detailSpeed); - j.at("detailStrength").get_to(p.detailStrength); - j.at("densityMultiplier").get_to(p.densityMultiplier); - j.at("heightStretch").get_to(p.heightStretch); - j.at("darkEdgeFocus").get_to(p.darkEdgeFocus); - j.at("darkEdgeAmbient").get_to(p.darkEdgeAmbient); - j.at("scattering").get_to(p.scattering); - j.at("enable").get_to(p.enable); - j.at("castShadow").get_to(p.castShadow); - j.at("stochasticOcclusionSampling").get_to(p.stochasticOcclusionSampling); - } - - void to_json(json& j, const Sky& p) { - j = json { - {"planetCenter", p.planetCenter}, - {"planetRadius", p.planetRadius} - }; - - if (p.atmosphere) - j["atmosphere"] = *p.atmosphere; - if (p.clouds) - j["clouds"] = *p.clouds; - if (p.probe) - j["probe"] = *p.probe; - } - - void from_json(const json& j, Sky& p) { - j.at("planetCenter").get_to(p.planetCenter); - j.at("planetRadius").get_to(p.planetRadius); - - if (j.contains("atmosphere")) { - p.atmosphere = CreateRef(); - *p.atmosphere = j["atmosphere"]; - } - if (j.contains("clouds")) { - p.clouds = CreateRef(); - *p.clouds = j["clouds"]; - } - if (j.contains("probe")) { - p.probe = CreateRef(); - *p.probe = j["probe"]; - } - } + void from_json(const json& j, Sky& p); } \ No newline at end of file diff --git a/src/engine/lighting/Reflection.h b/src/engine/lighting/Reflection.h index a684ba557..59ec29ab9 100644 --- a/src/engine/lighting/Reflection.h +++ b/src/engine/lighting/Reflection.h @@ -18,7 +18,7 @@ namespace Atlas { float spatialFilterStrength = 5.0f; float temporalWeight = 0.95f; - float historyClipMax = 0.1f; + float historyClipMax = 1.0f; float currentClipFactor = 2.0f; bool enable = true; diff --git a/src/engine/lighting/Shadow.h b/src/engine/lighting/Shadow.h index 6ea265349..3c7d963e4 100644 --- a/src/engine/lighting/Shadow.h +++ b/src/engine/lighting/Shadow.h @@ -22,6 +22,8 @@ namespace Atlas { mat4 frustumMatrix; mat4 terrainFrustumMatrix; + vec4 orthoSize; + }; class Shadow { @@ -55,8 +57,9 @@ namespace Atlas { Texture::Cubemap cubemap; bool isCascaded = false; + bool followMainCamera = false; bool useCubemap = false; - bool allowDynamicActors = false; + bool allowDynamicEntities = false; bool allowTerrain = false; bool longRange = false; bool update = true; diff --git a/src/engine/loader/AssetLoader.cpp b/src/engine/loader/AssetLoader.cpp index 15c8b12f1..d3e875e55 100644 --- a/src/engine/loader/AssetLoader.cpp +++ b/src/engine/loader/AssetLoader.cpp @@ -63,6 +63,12 @@ namespace Atlas { } + std::string AssetLoader::GetAssetDirectory() { + + return assetDirectory; + + } + bool AssetLoader::FileExists(const std::string& filename) { auto assetDir = Common::Path::GetAbsolute(assetDirectory); diff --git a/src/engine/loader/AssetLoader.h b/src/engine/loader/AssetLoader.h index 91e75a2d8..2e8c0e7f7 100644 --- a/src/engine/loader/AssetLoader.h +++ b/src/engine/loader/AssetLoader.h @@ -37,6 +37,11 @@ namespace Atlas { */ static void SetAssetDirectory(const std::string& directory); + /** + * Gets the asset directory + */ + static std::string GetAssetDirectory(); + /** * Check whether a file exists in the asset directory * @return True if file exists, false otherwise diff --git a/src/engine/physics/Body.cpp b/src/engine/physics/Body.cpp index 375bb98d4..25c1c5d23 100644 --- a/src/engine/physics/Body.cpp +++ b/src/engine/physics/Body.cpp @@ -73,6 +73,13 @@ namespace Atlas::Physics { } + uint64_t Body::GetUserData() const { + + AE_ASSERT(world != nullptr && "Physics world is invalid"); + return world->GetUserData(bodyId); + + } + Physics::BodyCreationSettings Body::GetBodyCreationSettings() { AE_ASSERT(world != nullptr && "Physics world is invalid"); diff --git a/src/engine/physics/Body.h b/src/engine/physics/Body.h index da0f0b733..870578edd 100644 --- a/src/engine/physics/Body.h +++ b/src/engine/physics/Body.h @@ -35,6 +35,8 @@ namespace Atlas::Physics { float GetFriction(); + uint64_t GetUserData() const; + virtual BodyCreationSettings GetBodyCreationSettings(); BodyID bodyId; diff --git a/src/engine/physics/PhysicsSerializer.cpp b/src/engine/physics/PhysicsSerializer.cpp index 8b1378917..c095e9475 100644 --- a/src/engine/physics/PhysicsSerializer.cpp +++ b/src/engine/physics/PhysicsSerializer.cpp @@ -1 +1,267 @@ +#include "PhysicsSerializer.h" +#include +#include +#include +#include +#include + +namespace Atlas::Physics { + + void to_json(json& j, const MeshShapeSettings& p) { + j = json { + {"scale", p.scale}, + }; + + if (p.mesh.IsValid()) { + j["resourcePath"] = p.mesh.GetResource()->path; + } + } + + void from_json(const json& j, MeshShapeSettings& p) { + j.at("scale").get_to(p.scale); + + if (j.contains("resourcePath")) { + std::string resourcePath; + j.at("resourcePath").get_to(resourcePath); + + p.mesh = ResourceManager::GetOrLoadResourceWithLoaderAsync(resourcePath, + ResourceOrigin::User, Loader::ModelLoader::LoadMesh, false, 8192); + } + } + + void to_json(json& j, const SphereShapeSettings& p) { + j = json { + {"radius", p.radius}, + {"density", p.density}, + {"scale", p.scale}, + }; + } + + void from_json(const json& j, SphereShapeSettings& p) { + j.at("radius").get_to(p.radius); + j.at("density").get_to(p.density); + j.at("scale").get_to(p.scale); + } + + void to_json(json& j, const BoundingBoxShapeSettings& p) { + j = json { + {"aabb", p.aabb}, + {"density", p.density}, + {"scale", p.scale}, + }; + } + + void from_json(const json& j, BoundingBoxShapeSettings& p) { + j.at("aabb").get_to(p.aabb); + j.at("density").get_to(p.density); + j.at("scale").get_to(p.scale); + } + + void to_json(json& j, const CapsuleShapeSettings& p) { + j = json{ + {"height", p.height}, + {"radius", p.radius}, + {"density", p.density}, + {"scale", p.scale}, + }; + } + + void from_json(const json& j, CapsuleShapeSettings& p) { + j.at("height").get_to(p.height); + j.at("radius").get_to(p.radius); + j.at("density").get_to(p.density); + j.at("scale").get_to(p.scale); + } + + void to_json(json& j, const HeightFieldShapeSettings& p) { + j = json { + {"heightData", p.heightData}, + {"translation", p.translation}, + {"scale", p.scale}, + }; + } + + void from_json(const json& j, HeightFieldShapeSettings& p) { + j.at("heightData").get_to(p.heightData); + j.at("translation").get_to(p.translation); + j.at("scale").get_to(p.scale); + } + + void to_json(json& j, const Shape& p) { + if (p.type == ShapeType::Mesh) { + auto meshSettings = static_cast(p.settings.get()); + j["meshSettings"] = *meshSettings; + } + else if (p.type == ShapeType::Sphere) { + auto sphereSettings = static_cast(p.settings.get()); + j["sphereSettings"] = *sphereSettings; + } + else if (p.type == ShapeType::BoundingBox) { + auto boundingBoxSettings = static_cast(p.settings.get()); + j["boundingBoxSettings"] = *boundingBoxSettings; + } + else if (p.type == ShapeType::Capsule) { + auto capsuleShapeSettings = static_cast(p.settings.get()); + j["capsuleShapeSettings"] = *capsuleShapeSettings; + } + else if (p.type == ShapeType::HeightField) { + auto heightFieldSettings = static_cast(p.settings.get()); + j["heightFieldSettings"] = *heightFieldSettings; + } + } + + void from_json(const json& j, Ref& p) { + if (j.contains("meshSettings")) { + MeshShapeSettings settings = j["meshSettings"]; + p = ShapesManager::CreateShape(settings); + } + else if (j.contains("sphereSettings")) { + SphereShapeSettings settings = j["sphereSettings"]; + p = ShapesManager::CreateShape(settings); + } + else if (j.contains("boundingBoxSettings")) { + BoundingBoxShapeSettings settings = j["boundingBoxSettings"]; + p = ShapesManager::CreateShape(settings); + } + else if (j.contains("capsuleShapeSettings")) { + CapsuleShapeSettings settings = j["capsuleShapeSettings"]; + p = ShapesManager::CreateShape(settings); + } + else if (j.contains("heightFieldSettings")) { + HeightFieldShapeSettings settings = j["heightFieldSettings"]; + p = ShapesManager::CreateShape(settings); + } + } + + void to_json(json& j, const BodyCreationSettings& p) { + // Keep default value and compare, no need to write everything + BodyCreationSettings d; + if (d.objectLayer != p.objectLayer) + j["objectLayer"] = p.objectLayer; + if (d.motionQuality != p.motionQuality) + j["motionQuality"] = p.motionQuality; + if (d.linearVelocity != p.linearVelocity) + j["linearVelocity"] = p.linearVelocity; + if (d.angularVelocity != p.angularVelocity) + j["angularVelocity"] = p.angularVelocity; + if (d.friction != p.friction) + j["friction"] = p.friction; + if (d.restitution != p.restitution) + j["restitution"] = p.restitution; + if (d.linearDampening != p.linearDampening) + j["linearDampening"] = p.linearDampening; + if (d.angularDampening != p.angularDampening) + j["angularDampening"] = p.angularDampening; + if (d.gravityFactor != p.gravityFactor) + j["gravityFactor"] = p.gravityFactor; + if (p.shape) + j["shape"] = *p.shape; + } + + void from_json(const json& j, BodyCreationSettings& p) { + if (j.contains("objectLayer")) + p.objectLayer = j["objectLayer"]; + if (j.contains("motionQuality")) + p.motionQuality = j["motionQuality"]; + if (j.contains("linearVelocity")) + p.linearVelocity = j["linearVelocity"]; + if (j.contains("angularVelocity")) + p.angularVelocity = j["angularVelocity"]; + if (j.contains("friction")) + p.friction = j["friction"]; + if (j.contains("restitution")) + p.restitution = j["restitution"]; + if (j.contains("linearDampening")) + p.linearDampening = j["linearDampening"]; + if (j.contains("angularDampening")) + p.angularDampening = j["angularDampening"]; + if (j.contains("gravityFactor")) + p.gravityFactor = j["gravityFactor"]; + if (j.contains("shape")) { + p.shape = j["shape"]; + } + } + + void to_json(json& j, const PlayerCreationSettings& p) { + // Keep default value and compare, no need to write everything + PlayerCreationSettings d; + if (d.maxSlopeAngle != p.maxSlopeAngle) + j["maxSlopeAngle"] = p.maxSlopeAngle; + if (d.up != p.up) + j["up"] = p.up; + if (d.mass != p.mass) + j["mass"] = p.mass; + if (d.maxStrength != p.maxStrength) + j["maxStrength"] = p.maxStrength; + if (d.predictiveContactDistance != p.predictiveContactDistance) + j["predictiveContactDistance"] = p.predictiveContactDistance; + if (d.shapePadding != p.shapePadding) + j["shapePadding"] = p.shapePadding; + if (d.shapeOffset != p.shapeOffset) + j["shapeOffset"] = p.shapeOffset; + if (p.shape) + j["shape"] = *p.shape; + } + + void from_json(const json& j, PlayerCreationSettings& p) { + if (j.contains("maxSlopeAngle")) + p.maxSlopeAngle = j["maxSlopeAngle"]; + if (j.contains("up")) + p.up = j["up"]; + if (j.contains("mass")) + p.mass = j["mass"]; + if (j.contains("maxStrength")) + p.maxStrength = j["maxStrength"]; + if (j.contains("predictiveContactDistance")) + p.predictiveContactDistance = j["predictiveContactDistance"]; + if (j.contains("shapePadding")) + p.shapePadding = j["shapePadding"]; + if (j.contains("shapeOffset")) + p.shapeOffset = j["shapeOffset"]; + if (j.contains("shape")) { + p.shape = j["shape"]; + } + } + + void SerializePhysicsWorld(json& j, Ref& physicsWorld) { + + auto& system = physicsWorld->system; + auto& bodyInterface = system->GetBodyInterface(); + + JPH::BodyIDVector bodyIds; + system->GetBodies(bodyIds); + + auto& bodyLockInterface = system->GetBodyLockInterface(); + + std::unordered_map bodyCreationSettings; + + for (auto bodyId : bodyIds) { + JPH::BodyLockRead lock(bodyLockInterface, bodyId); + if (lock.Succeeded()) { + const auto& body = lock.GetBody(); + + if (body.IsRigidBody()) { + JPH::BodyCreationSettings settings = body.GetBodyCreationSettings(); + BodyCreationSettings creationSettings; + creationSettings.SetSettings(settings); + creationSettings.shape = physicsWorld->bodyToShapeMap[bodyId]; + bodyCreationSettings[bodyId.GetIndex()] = creationSettings; + } + else { + JPH::SoftBodyCreationSettings settings = body.GetSoftBodyCreationSettings(); + } + } + } + + j["bodies"] = bodyCreationSettings; + + } + + void DeserializePhysicsWorld(const json& j, std::unordered_map& bodyCreationMap) { + + bodyCreationMap = j["bodies"]; + + } + +} \ No newline at end of file diff --git a/src/engine/physics/PhysicsSerializer.h b/src/engine/physics/PhysicsSerializer.h index 2fef24815..2990fa9b9 100644 --- a/src/engine/physics/PhysicsSerializer.h +++ b/src/engine/physics/PhysicsSerializer.h @@ -8,268 +8,42 @@ #include #include -#include -#include -#include -#include -#include - namespace Atlas::Physics { - void to_json(json& j, const MeshShapeSettings& p) { - j = json { - {"scale", p.scale}, - }; - - if (p.mesh.IsValid()) { - j["resourcePath"] = p.mesh.GetResource()->path; - } - } - - void from_json(const json& j, MeshShapeSettings& p) { - j.at("scale").get_to(p.scale); - - if (j.contains("resourcePath")) { - std::string resourcePath; - j.at("resourcePath").get_to(resourcePath); - - p.mesh = ResourceManager::GetOrLoadResourceWithLoaderAsync(resourcePath, - ResourceOrigin::User, Loader::ModelLoader::LoadMesh, false, 8192); - } - } - - void to_json(json& j, const SphereShapeSettings& p) { - j = json { - {"radius", p.radius}, - {"density", p.density}, - {"scale", p.scale}, - }; - } - - void from_json(const json& j, SphereShapeSettings& p) { - j.at("radius").get_to(p.radius); - j.at("density").get_to(p.density); - j.at("scale").get_to(p.scale); - } - - void to_json(json& j, const BoundingBoxShapeSettings& p) { - j = json { - {"aabb", p.aabb}, - {"density", p.density}, - {"scale", p.scale}, - }; - } - - void from_json(const json& j, BoundingBoxShapeSettings& p) { - j.at("aabb").get_to(p.aabb); - j.at("density").get_to(p.density); - j.at("scale").get_to(p.scale); - } - - void to_json(json& j, const CapsuleShapeSettings& p) { - j = json{ - {"height", p.height}, - {"radius", p.radius}, - {"density", p.density}, - {"scale", p.scale}, - }; - } - - void from_json(const json& j, CapsuleShapeSettings& p) { - j.at("height").get_to(p.height); - j.at("radius").get_to(p.radius); - j.at("density").get_to(p.density); - j.at("scale").get_to(p.scale); - } - - void to_json(json& j, const HeightFieldShapeSettings& p) { - j = json { - {"heightData", p.heightData}, - {"translation", p.translation}, - {"scale", p.scale}, - }; - } - - void from_json(const json& j, HeightFieldShapeSettings& p) { - j.at("heightData").get_to(p.heightData); - j.at("translation").get_to(p.translation); - j.at("scale").get_to(p.scale); - } - - void to_json(json& j, const Shape& p) { - if (p.type == ShapeType::Mesh) { - auto meshSettings = static_cast(p.settings.get()); - j["meshSettings"] = *meshSettings; - } - else if (p.type == ShapeType::Sphere) { - auto sphereSettings = static_cast(p.settings.get()); - j["sphereSettings"] = *sphereSettings; - } - else if (p.type == ShapeType::BoundingBox) { - auto boundingBoxSettings = static_cast(p.settings.get()); - j["boundingBoxSettings"] = *boundingBoxSettings; - } - else if (p.type == ShapeType::Capsule) { - auto capsuleShapeSettings = static_cast(p.settings.get()); - j["capsuleShapeSettings"] = *capsuleShapeSettings; - } - else if (p.type == ShapeType::HeightField) { - auto heightFieldSettings = static_cast(p.settings.get()); - j["heightFieldSettings"] = *heightFieldSettings; - } - } + void to_json(json& j, const MeshShapeSettings& p); - void from_json(const json& j, Ref& p) { - if (j.contains("meshSettings")) { - MeshShapeSettings settings = j["meshSettings"]; - p = ShapesManager::CreateShape(settings); - } - else if (j.contains("sphereSettings")) { - SphereShapeSettings settings = j["sphereSettings"]; - p = ShapesManager::CreateShape(settings); - } - else if (j.contains("boundingBoxSettings")) { - BoundingBoxShapeSettings settings = j["boundingBoxSettings"]; - p = ShapesManager::CreateShape(settings); - } - else if (j.contains("capsuleShapeSettings")) { - CapsuleShapeSettings settings = j["capsuleShapeSettings"]; - p = ShapesManager::CreateShape(settings); - } - else if (j.contains("heightFieldSettings")) { - HeightFieldShapeSettings settings = j["heightFieldSettings"]; - p = ShapesManager::CreateShape(settings); - } - } + void from_json(const json& j, MeshShapeSettings& p); - void to_json(json& j, const BodyCreationSettings& p) { - // Keep default value and compare, no need to write everything - BodyCreationSettings d; - if (d.objectLayer != p.objectLayer) - j["objectLayer"] = p.objectLayer; - if (d.motionQuality != p.motionQuality) - j["motionQuality"] = p.motionQuality; - if (d.linearVelocity != p.linearVelocity) - j["linearVelocity"] = p.linearVelocity; - if (d.angularVelocity != p.angularVelocity) - j["angularVelocity"] = p.angularVelocity; - if (d.friction != p.friction) - j["friction"] = p.friction; - if (d.restitution != p.restitution) - j["restitution"] = p.restitution; - if (d.linearDampening != p.linearDampening) - j["linearDampening"] = p.linearDampening; - if (d.angularDampening != p.angularDampening) - j["angularDampening"] = p.angularDampening; - if (d.gravityFactor != p.gravityFactor) - j["gravityFactor"] = p.gravityFactor; - if (p.shape) - j["shape"] = *p.shape; - } + void to_json(json& j, const SphereShapeSettings& p); - void from_json(const json& j, BodyCreationSettings& p) { - if (j.contains("objectLayer")) - p.objectLayer = j["objectLayer"]; - if (j.contains("motionQuality")) - p.motionQuality = j["motionQuality"]; - if (j.contains("linearVelocity")) - p.linearVelocity = j["linearVelocity"]; - if (j.contains("angularVelocity")) - p.angularVelocity = j["angularVelocity"]; - if (j.contains("friction")) - p.friction = j["friction"]; - if (j.contains("restitution")) - p.restitution = j["restitution"]; - if (j.contains("linearDampening")) - p.linearDampening = j["linearDampening"]; - if (j.contains("angularDampening")) - p.angularDampening = j["angularDampening"]; - if (j.contains("gravityFactor")) - p.gravityFactor = j["gravityFactor"]; - if (j.contains("shape")) { - p.shape = j["shape"]; - } - } + void from_json(const json& j, SphereShapeSettings& p); - void to_json(json& j, const PlayerCreationSettings& p) { - // Keep default value and compare, no need to write everything - PlayerCreationSettings d; - if (d.maxSlopeAngle != p.maxSlopeAngle) - j["maxSlopeAngle"] = p.maxSlopeAngle; - if (d.up != p.up) - j["up"] = p.up; - if (d.mass != p.mass) - j["mass"] = p.mass; - if (d.maxStrength != p.maxStrength) - j["maxStrength"] = p.maxStrength; - if (d.predictiveContactDistance != p.predictiveContactDistance) - j["predictiveContactDistance"] = p.predictiveContactDistance; - if (d.shapePadding != p.shapePadding) - j["shapePadding"] = p.shapePadding; - if (d.shapeOffset != p.shapeOffset) - j["shapeOffset"] = p.shapeOffset; - if (p.shape) - j["shape"] = *p.shape; - } + void to_json(json& j, const BoundingBoxShapeSettings& p); - void from_json(const json& j, PlayerCreationSettings& p) { - if (j.contains("maxSlopeAngle")) - p.maxSlopeAngle = j["maxSlopeAngle"]; - if (j.contains("up")) - p.up = j["up"]; - if (j.contains("mass")) - p.mass = j["mass"]; - if (j.contains("maxStrength")) - p.maxStrength = j["maxStrength"]; - if (j.contains("predictiveContactDistance")) - p.predictiveContactDistance = j["predictiveContactDistance"]; - if (j.contains("shapePadding")) - p.shapePadding = j["shapePadding"]; - if (j.contains("shapeOffset")) - p.shapeOffset = j["shapeOffset"]; - if (j.contains("shape")) { - p.shape = j["shape"]; - } - } + void from_json(const json& j, BoundingBoxShapeSettings& p); - void SerializePhysicsWorld(json& j, Ref& physicsWorld) { + void to_json(json& j, const CapsuleShapeSettings& p); - auto& system = physicsWorld->system; - auto& bodyInterface = system->GetBodyInterface(); + void from_json(const json& j, CapsuleShapeSettings& p); - JPH::BodyIDVector bodyIds; - system->GetBodies(bodyIds); + void to_json(json& j, const HeightFieldShapeSettings& p); - auto& bodyLockInterface = system->GetBodyLockInterface(); + void from_json(const json& j, HeightFieldShapeSettings& p); - std::unordered_map bodyCreationSettings; + void to_json(json& j, const Shape& p); - for (auto bodyId : bodyIds) { - JPH::BodyLockRead lock(bodyLockInterface, bodyId); - if (lock.Succeeded()) { - const auto& body = lock.GetBody(); + void from_json(const json& j, Ref& p); - if (body.IsRigidBody()) { - JPH::BodyCreationSettings settings = body.GetBodyCreationSettings(); - BodyCreationSettings creationSettings; - creationSettings.SetSettings(settings); - creationSettings.shape = physicsWorld->bodyToShapeMap[bodyId]; - bodyCreationSettings[bodyId.GetIndex()] = creationSettings; - } - else { - JPH::SoftBodyCreationSettings settings = body.GetSoftBodyCreationSettings(); - } - } - } + void to_json(json& j, const BodyCreationSettings& p); - j["bodies"] = bodyCreationSettings; + void from_json(const json& j, BodyCreationSettings& p); - } + void to_json(json& j, const PlayerCreationSettings& p); - void DeserializePhysicsWorld(json& j, std::unordered_map& bodyCreationMap) { + void from_json(const json& j, PlayerCreationSettings& p); - bodyCreationMap = j["bodies"]; + void SerializePhysicsWorld(json& j, Ref& physicsWorld) ; - } + void DeserializePhysicsWorld(const json& j, std::unordered_map& bodyCreationMap); } \ No newline at end of file diff --git a/src/engine/physics/PhysicsWorld.cpp b/src/engine/physics/PhysicsWorld.cpp index 845164172..b13b9b24e 100644 --- a/src/engine/physics/PhysicsWorld.cpp +++ b/src/engine/physics/PhysicsWorld.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include +#include +#include namespace Atlas { @@ -52,7 +56,7 @@ namespace Atlas { } - Body PhysicsWorld::CreateBody(const BodyCreationSettings& bodyCreationSettings, const mat4& matrix) { + Body PhysicsWorld::CreateBody(const BodyCreationSettings& bodyCreationSettings, const mat4& matrix, uint64_t userData) { auto& bodyInterface = system->GetBodyInterface(); @@ -64,6 +68,7 @@ namespace Atlas { settings.mPosition = pos; settings.mRotation = quat; + settings.mUserData = userData; auto bodyId = bodyInterface.CreateAndAddBody(settings, JPH::EActivation::Activate); bodyToShapeMap[bodyId] = bodyCreationSettings.shape; @@ -170,6 +175,13 @@ namespace Atlas { } + uint64_t PhysicsWorld::GetUserData(BodyID bodyId) { + + auto& bodyInterface = system->GetBodyInterface(); + return bodyInterface.GetUserData(bodyId); + + } + void PhysicsWorld::ChangeShape(BodyID bodyId, Ref shape) { auto& bodyInterface = system->GetBodyInterface(); @@ -212,6 +224,40 @@ namespace Atlas { } + Volume::RayResult PhysicsWorld::CastRay(Volume::Ray& ray) { + + const float farDist = 10e9f; + + JPH::RayCastResult hit; + // Actually direction is not really a direction but one endpoint + JPH::RRayCast rayCast{ VecToJPHVec(ray.origin), VecToJPHVec(ray.direction * farDist) }; + + Volume::RayResult result; + + JPH::RayCastSettings rayCastSettings = { + .mBackFaceMode = JPH::EBackFaceMode::CollideWithBackFaces + }; + + if (system->GetNarrowPhaseQuery().CastRay(rayCast, hit)) { + auto& bodyLockInterface = system->GetBodyLockInterface(); + + JPH::BodyLockRead lock(bodyLockInterface, hit.mBodyID); + if (lock.Succeeded()) { + const auto& body = lock.GetBody(); + + auto normal = body.GetWorldSpaceSurfaceNormal(hit.mSubShapeID2, rayCast.GetPointOnRay(hit.mFraction)); + result.normal = JPHVecToVec(normal); + } + + result.valid = true; + result.hitDistance = hit.mFraction * farDist; + result.data = { hit.mBodyID, this }; + } + + return result; + + } + void PhysicsWorld::OptimizeBroadphase() { system->OptimizeBroadPhase(); diff --git a/src/engine/physics/PhysicsWorld.h b/src/engine/physics/PhysicsWorld.h index e2dbebfcf..af56b9ee0 100644 --- a/src/engine/physics/PhysicsWorld.h +++ b/src/engine/physics/PhysicsWorld.h @@ -1,6 +1,7 @@ #pragma once #include "../System.h" +#include "../volume/Ray.h" #include "ShapesManager.h" #include "BodyCreation.h" #include "Body.h" @@ -37,7 +38,7 @@ namespace Atlas { void Update(float deltaTime); - Body CreateBody(const BodyCreationSettings& bodyCreationSettings, const mat4& matrix); + Body CreateBody(const BodyCreationSettings& bodyCreationSettings, const mat4& matrix, uint64_t userData = 0); void DestroyBody(Body body); @@ -61,6 +62,8 @@ namespace Atlas { float GetFriction(BodyID bodyId); + uint64_t GetUserData(BodyID bodyId); + void ChangeShape(BodyID bodyId, Ref shape); BodyCreationSettings GetBodyCreationSettings(BodyID bodyId); @@ -69,6 +72,8 @@ namespace Atlas { vec3 GetGravity(); + Volume::RayResult CastRay(Volume::Ray& ray); + void OptimizeBroadphase(); void SaveState(); diff --git a/src/engine/renderer/DirectLightRenderer.cpp b/src/engine/renderer/DirectLightRenderer.cpp index 9139fa02a..ab44f142f 100644 --- a/src/engine/renderer/DirectLightRenderer.cpp +++ b/src/engine/renderer/DirectLightRenderer.cpp @@ -90,6 +90,7 @@ namespace Atlas { uniformBuffer.SetData(&uniforms, 0); + pipelineConfig.ManageMacro("SHADOWS", light.shadow != nullptr); pipelineConfig.ManageMacro("SCREEN_SPACE_SHADOWS", sss && sss->enable); pipelineConfig.ManageMacro("CLOUD_SHADOWS", clouds && clouds->enable && clouds->castShadow); auto pipeline = PipelineManager::GetPipeline(pipelineConfig); diff --git a/src/engine/renderer/MainRenderer.cpp b/src/engine/renderer/MainRenderer.cpp index 6765a22ad..648c029da 100644 --- a/src/engine/renderer/MainRenderer.cpp +++ b/src/engine/renderer/MainRenderer.cpp @@ -69,6 +69,8 @@ namespace Atlas { textRenderer.Init(device); textureRenderer.Init(device); + font = Atlas::CreateRef("font/roboto.ttf", 22.0f, 5); + } void MainRenderer::RenderScene(Ref viewport, Ref target, Ref scene, @@ -328,8 +330,11 @@ namespace Atlas { // This was needed after the ocean renderer, if we ever want to have alpha transparency we need it again // downscaleRenderer.Downscale(target, commandList); - if (primitiveBatch) - RenderPrimitiveBatch(viewport, target, primitiveBatch, scene->GetMainCamera(), commandList); + commandList->BeginRenderPass(target->afterLightingRenderPass, target->afterLightingFrameBuffer); + + textRenderer.Render(target, scene, commandList); + + commandList->EndRenderPass(); { volumetricCloudRenderer.Render(target, scene, commandList); @@ -339,6 +344,9 @@ namespace Atlas { oceanRenderer.Render(target, scene, commandList); + if (primitiveBatch) + RenderPrimitiveBatch(viewport, target, primitiveBatch, scene->GetMainCamera(), commandList); + { taaRenderer.Render(target, scene, commandList); diff --git a/src/engine/renderer/MainRenderer.h b/src/engine/renderer/MainRenderer.h index 04b250370..519ecc188 100644 --- a/src/engine/renderer/MainRenderer.h +++ b/src/engine/renderer/MainRenderer.h @@ -64,6 +64,8 @@ namespace Atlas { AtmosphereRenderer atmosphereRenderer; PathTracingRenderer pathTracingRenderer; + Ref font; + private: struct PackedMaterial { diff --git a/src/engine/renderer/PostProcessRenderer.cpp b/src/engine/renderer/PostProcessRenderer.cpp index 35512310f..df2660d6a 100644 --- a/src/engine/renderer/PostProcessRenderer.cpp +++ b/src/engine/renderer/PostProcessRenderer.cpp @@ -309,6 +309,7 @@ namespace Atlas { auto pipelineDesc = Graphics::GraphicsPipelineDesc { .swapChain = device->swapChain }; + pipelineDesc.assemblyInputInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; return PipelineConfig(shaderConfig, pipelineDesc, GetMacros()); @@ -323,6 +324,8 @@ namespace Atlas { auto pipelineDesc = Graphics::GraphicsPipelineDesc { .frameBuffer = frameBuffer }; + pipelineDesc.assemblyInputInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + return PipelineConfig(shaderConfig, pipelineDesc, GetMacros()); } diff --git a/src/engine/renderer/ShadowRenderer.cpp b/src/engine/renderer/ShadowRenderer.cpp index 9ce463e1d..83a72ce37 100644 --- a/src/engine/renderer/ShadowRenderer.cpp +++ b/src/engine/renderer/ShadowRenderer.cpp @@ -154,7 +154,8 @@ namespace Atlas { auto& light = entity.GetComponent(); auto& shadow = light.shadow; - + + /* if (lightMap.contains(entity)) { auto frameBuffer = lightMap[entity]; if (frameBuffer->extent.width == shadow->resolution || @@ -162,6 +163,7 @@ namespace Atlas { return frameBuffer; } } + */ Graphics::RenderPassDepthAttachment attachment = { .imageFormat = shadow->useCubemap ? shadow->cubemap.format : diff --git a/src/engine/renderer/TextRenderer.cpp b/src/engine/renderer/TextRenderer.cpp index 6f56549f0..b5e946fc5 100644 --- a/src/engine/renderer/TextRenderer.cpp +++ b/src/engine/renderer/TextRenderer.cpp @@ -9,12 +9,28 @@ namespace Atlas { this->device = device; - Helper::GeometryHelper::GenerateRectangleVertexArray(vertexArray); - auto bufferUsage = Buffer::BufferUsageBits::StorageBufferBit | Buffer::BufferUsageBits::MultiBufferedBit | Buffer::BufferUsageBits::HostAccessBit; instanceBuffer = Buffer::Buffer(bufferUsage, sizeof(vec4), 16384); - uniformBuffer = Buffer::UniformBuffer(sizeof(Uniforms)); + + } + + void TextRenderer::Render(Ref target, Ref scene, Graphics::CommandList* commandList) { + + auto textSubset = scene->GetSubset(); + + // This whole thing isn't really optimized right now, but as long as it's not a bottleneck it's fine right now + for (auto entity : textSubset) { + + auto textComp = textSubset.Get(entity); + if (!textComp.font.IsLoaded()) + continue; + + RenderOutlined3D(commandList, textComp.font.Get(), textComp.text, textComp.GetTransformedPosition(), + textComp.GetTransformedRotation(), textComp.halfSize, textComp.textColor, textComp.outlineColor, + textComp.outlineFactor, textComp.textScale, target->afterLightingFrameBuffer); + + } } @@ -63,7 +79,7 @@ namespace Atlas { int32_t characterCount; - auto pipelineConfig = GeneratePipelineConfig(frameBuffer); + auto pipelineConfig = GetPipelineConfig(frameBuffer); auto pipeline = PipelineManager::GetPipeline(pipelineConfig); commandList->BindPipeline(pipeline); @@ -74,29 +90,71 @@ namespace Atlas { auto instances = CalculateCharacterInstances(font, text, &characterCount); instanceBuffer.SetData(instances.data(), frameCharacterCount, characterCount); - Uniforms uniforms { - .pMatrix = glm::ortho(0.0f, width, 0.0f, height), + PushConstants constants { .clipArea = clipArea, .blendArea = blendArea, .characterColor = color, .outlineColor = outlineColor, .textOffset = vec2(x, y), + .renderArea = vec2(width, height), .textScale = scale, .outlineScale = outlineScale, .edgeValue = float(font->edgeValue) / 255.0f, .smoothness = font->smoothness }; - uniformBuffer.SetData(&uniforms, 0); + commandList->PushConstants("constants", &constants); commandList->BindImage(font->glyphTexture.image, font->glyphTexture.sampler, 3, 0); commandList->BindBuffer(font->glyphBuffer.Get(), 3, 1); + commandList->BindBuffer(instanceBuffer.GetMultiBuffer(), 3, 2); + + commandList->Draw(4, uint32_t(characterCount), 0, frameCharacterCount); + + frameCharacterCount += characterCount; + + } + + void TextRenderer::RenderOutlined3D(Graphics::CommandList* commandList, Ref font, const std::string& text, + vec3 position, quat rotation, vec2 halfSize, vec4 color, vec4 outlineColor, float outlineScale, + float scale, const Ref& frameBuffer) { + + scale /= 16.0f; + + int32_t characterCount; + auto pipelineConfig = GetPipelineConfig(frameBuffer, true); + auto pipeline = PipelineManager::GetPipeline(pipelineConfig); + + commandList->BindPipeline(pipeline); + + auto instances = CalculateCharacterInstances3D(font, text, halfSize, scale, &characterCount); + instanceBuffer.SetData(instances.data(), frameCharacterCount, characterCount); + + auto right = glm::mat3_cast(rotation) * glm::vec3(1.0f, 0.0f, 0.0f); + auto down = glm::mat3_cast(rotation) * glm::vec3(0.0f, -1.0f, 0.0f); + + PushConstants3D constants { + .position = vec4(position, 1.0f), + .right = vec4(right, 1.0f), + .down = vec4(down, 1.0f), + .characterColor = color, + .outlineColor = outlineColor, + .renderHalfSize = halfSize, + .textScale = scale, + .outlineScale = outlineScale, + .edgeValue = float(font->edgeValue) / 255.0f, + .smoothness = font->smoothness + }; + + commandList->PushConstants("constants", &constants); + + commandList->BindImage(font->glyphTexture.image, font->glyphTexture.sampler, 3, 0); + + commandList->BindBuffer(font->glyphBuffer.Get(), 3, 1); commandList->BindBuffer(instanceBuffer.GetMultiBuffer(), 3, 2); - commandList->BindBuffer(uniformBuffer.Get(), 3, 3); - vertexArray.Bind(commandList); commandList->Draw(4, uint32_t(characterCount), 0, frameCharacterCount); frameCharacterCount += characterCount; @@ -148,7 +206,53 @@ namespace Atlas { } - PipelineConfig TextRenderer::GeneratePipelineConfig(const Ref& frameBuffer) { + std::vector TextRenderer::CalculateCharacterInstances3D(Ref& font, const std::string& text, + vec2 halfSize, float textSize, int32_t* characterCount) { + + *characterCount = 0; + + auto instances = std::vector(text.length()); + + int32_t index = 0; + + float xOffset = 0.0f; + float yOffset = 0.0f; + + auto ctext = text.c_str(); + + auto nextGlyph = font->GetGlyphUTF8(ctext); + + while (nextGlyph->codepoint) { + + Glyph* glyph = nextGlyph; + + // We have at least one character per line, even if it overshoots the size a bit + if (xOffset * textSize > 2.0f * halfSize.x) { + xOffset = 0.0f; + yOffset += font->lineHeight; + } + + // Just visible characters should be rendered. + if (glyph->codepoint > 32 && glyph->texArrayIndex < AE_GPU_GLYPH_COUNT) { + instances[index].x = (glyph->offset.x + xOffset) * textSize; + instances[index].y = (glyph->offset.y + font->ascent + yOffset) * textSize; + instances[index].z = (float)glyph->texArrayIndex; + index++; + } + + nextGlyph = font->GetGlyphUTF8(ctext); + + xOffset += glyph->advance + glyph->kern[nextGlyph->codepoint]; + + } + + *characterCount = index; + + return instances; + + } + + PipelineConfig TextRenderer::GetPipelineConfig(const Ref& frameBuffer, bool threeDimensional) { auto shaderConfig = ShaderConfig { {"text.vsh", VK_SHADER_STAGE_VERTEX_BIT}, @@ -157,15 +261,19 @@ namespace Atlas { Graphics::GraphicsPipelineDesc pipelineDesc { .swapChain = device->swapChain, .frameBuffer = frameBuffer, - .vertexInputInfo = vertexArray.GetVertexInputState(), .assemblyInputInfo = Graphics::Initializers::InitPipelineInputAssemblyStateCreateInfo( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP), .depthStencilInputInfo = Graphics::Initializers::InitPipelineDepthStencilStateCreateInfo( - false, false, VK_COMPARE_OP_LESS_OR_EQUAL) + threeDimensional, threeDimensional, VK_COMPARE_OP_LESS_OR_EQUAL) }; // By default, an alpha blending is used - pipelineDesc.colorBlendAttachment.blendEnable = VK_TRUE; - return PipelineConfig(shaderConfig, pipelineDesc); + pipelineDesc.colorBlendAttachment.blendEnable = !threeDimensional; + pipelineDesc.rasterizer.cullMode = VK_CULL_MODE_NONE; + + std::vector macros; + if (threeDimensional) + macros.push_back("TEXT_3D"); + return PipelineConfig(shaderConfig, pipelineDesc, macros); } diff --git a/src/engine/renderer/TextRenderer.h b/src/engine/renderer/TextRenderer.h index 32735bd3d..144a3f991 100644 --- a/src/engine/renderer/TextRenderer.h +++ b/src/engine/renderer/TextRenderer.h @@ -17,6 +17,8 @@ namespace Atlas { void Init(Graphics::GraphicsDevice* device); + void Render(Ref target, Ref scene, Graphics::CommandList* commandList); + void Render(Graphics::CommandList* commandList, Ref viewport, Ref font, const std::string& text, float x, float y, vec4 color = vec4(1.0f), float scale = 1.0f, const Ref& frameBuffer = nullptr); @@ -33,16 +35,33 @@ namespace Atlas { const std::string& text, float x, float y, vec4 color, vec4 outlineColor, float outlineScale, vec4 clipArea, vec4 blendArea, float scale = 1.0f, const Ref& frameBuffer = nullptr); + void RenderOutlined3D(Graphics::CommandList* commandList, Ref font, const std::string& text, + vec3 position, quat rotation, vec2 halfSize, vec4 color, vec4 outlineColor, float outlineScale, + float scale = 1.0f, const Ref& frameBuffer = nullptr); + void Update(); private: - struct alignas(16) Uniforms { - mat4 pMatrix; + struct alignas(16) PushConstants { vec4 clipArea; vec4 blendArea; vec4 characterColor; vec4 outlineColor; vec2 textOffset; + vec2 renderArea; + float textScale; + float outlineScale; + float edgeValue; + float smoothness; + }; + + struct alignas(16) PushConstants3D { + vec4 position; + vec4 right; + vec4 down; + vec4 characterColor; + vec4 outlineColor; + vec2 renderHalfSize; float textScale; float outlineScale; float edgeValue; @@ -51,12 +70,12 @@ namespace Atlas { std::vector CalculateCharacterInstances(Ref& font, const std::string& text, int32_t* characterCount); - PipelineConfig GeneratePipelineConfig(const Ref& frameBuffer); + std::vector CalculateCharacterInstances3D(Ref& font, const std::string& text, vec2 halfSize, + float textScale, int32_t* characterCount); - Buffer::VertexArray vertexArray; + PipelineConfig GetPipelineConfig(const Ref& frameBuffer, bool threeDimensional = false); Buffer::Buffer instanceBuffer; - Buffer::UniformBuffer uniformBuffer; Ref renderPass; diff --git a/src/engine/renderer/VolumetricRenderer.cpp b/src/engine/renderer/VolumetricRenderer.cpp index c5b5e339c..95ebee151 100644 --- a/src/engine/renderer/VolumetricRenderer.cpp +++ b/src/engine/renderer/VolumetricRenderer.cpp @@ -60,7 +60,7 @@ namespace Atlas { for (auto& lightEntity : lightSubset) { auto& light = lightEntity.GetComponent(); - if (!light.shadow || light.type != LightType::DirectionalLight || + if (light.type != LightType::DirectionalLight || !light.volumetric || !fog || !fog->rayMarching) continue; @@ -83,22 +83,25 @@ namespace Atlas { uniforms.light.direction = vec4(direction, 0.0); uniforms.light.color = vec4(Common::ColorConverter::ConvertSRGBToLinear(light.color), 0.0); - uniforms.light.shadow.cascadeCount = shadow->viewCount; - - commandList->BindImage(shadow->maps.image, shadowSampler, 3, 2); - - auto& shadowUniform = uniforms.light.shadow; - for (int32_t i = 0; i < MAX_SHADOW_VIEW_COUNT + 1; i++) { - auto& cascadeUniform = shadowUniform.cascades[i]; - auto cascadeString = "light.shadow.cascades[" + std::to_string(i) + "]"; - if (i < shadow->viewCount) { - auto cascade = &shadow->views[i]; - cascadeUniform.distance = cascade->farDistance; - cascadeUniform.cascadeSpace = cascade->projectionMatrix * - cascade->viewMatrix * camera.invViewMatrix; - } - else { - cascadeUniform.distance = camera.farPlane; + + if (light.shadow) { + uniforms.light.shadow.cascadeCount = shadow->viewCount; + + commandList->BindImage(shadow->maps.image, shadowSampler, 3, 2); + + auto& shadowUniform = uniforms.light.shadow; + for (int32_t i = 0; i < MAX_SHADOW_VIEW_COUNT + 1; i++) { + auto& cascadeUniform = shadowUniform.cascades[i]; + auto cascadeString = "light.shadow.cascades[" + std::to_string(i) + "]"; + if (i < shadow->viewCount) { + auto cascade = &shadow->views[i]; + cascadeUniform.distance = cascade->farDistance; + cascadeUniform.cascadeSpace = cascade->projectionMatrix * + cascade->viewMatrix * camera.invViewMatrix; + } + else { + cascadeUniform.distance = camera.farPlane; + } } } @@ -158,6 +161,7 @@ namespace Atlas { volumetricUniformBuffer.SetData(&uniforms, 0); commandList->BindBuffer(volumetricUniformBuffer.Get(), 3, 7); + volumetricPipelineConfig.ManageMacro("SHADOWS", light.shadow != nullptr); volumetricPipelineConfig.ManageMacro("CLOUDS", cloudsEnabled); volumetricPipelineConfig.ManageMacro("CLOUD_SHADOWS", cloudShadowsEnabled); volumetricPipelineConfig.ManageMacro("OCEAN", oceanEnabled); diff --git a/src/engine/scene/EntitySerializer.cpp b/src/engine/scene/EntitySerializer.cpp new file mode 100644 index 000000000..83d9a66e6 --- /dev/null +++ b/src/engine/scene/EntitySerializer.cpp @@ -0,0 +1,119 @@ +#include "EntitySerializer.h" + +namespace Atlas::Scene { + + void EntityToJson(json& j, const Entity& p, Ref& scene, + std::set& insertedEntites) { + + AE_ASSERT(!insertedEntites.contains(p) && "Entity should only be present once in the hierarchy"); + + if (!insertedEntites.contains(p)) + insertedEntites.insert(p); + + j["id"] = size_t(p); + + if (p.HasComponent()) { + j["name"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["transform"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["mesh"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["light"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["camera"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["audio"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["audioVolume"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["rigidBody"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["player"] = p.GetComponent(); + } + if (p.HasComponent()) { + j["text"] = p.GetComponent(); + } + if (p.HasComponent()) { + // Need to check how to get this to work + auto& hierarchyComponent = p.GetComponent(); + std::vector entities; + for (auto entity : hierarchyComponent.GetChildren()) { + entities.emplace_back(); + EntityToJson(entities.back(), entity, scene, insertedEntites); + } + j["entities"] = entities; + j["root"] = hierarchyComponent.root; + } + } + + void EntityFromJson(const json& j, Entity& p, Ref& scene) { + if (j.contains("name")) { + NameComponent comp = j["name"]; + p.AddComponent(comp); + } + if (j.contains("transform")) { + TransformComponent comp = j["transform"]; + p.AddComponent(comp); + } + if (j.contains("mesh")) { + MeshComponent comp = j["mesh"]; + p.AddComponent(std::move(comp)); + } + if (j.contains("light")) { + LightComponent comp = j["light"]; + p.AddComponent(comp); + } + if (j.contains("camera")) { + CameraComponent comp = j["camera"]; + p.AddComponent(comp); + } + if (j.contains("audio")) { + AudioComponent comp = j["audio"]; + p.AddComponent(comp); + } + if (j.contains("audioVolume")) { + AudioVolumeComponent comp = j["audioVolume"]; + p.AddComponent(comp); + } + if (j.contains("rigidBody")) { + RigidBodyComponent comp = j["rigidBody"]; + p.AddComponent(comp); + } + if (j.contains("player")) { + PlayerComponent comp = j["player"]; + p.AddComponent(comp); + } + if (j.contains("text")) { + TextComponent comp = j["text"]; + p.AddComponent(comp); + } + if (j.contains("entities")) { + // We need to first push back to a temporary vector to not invalidate + // component references (i.e. when directly getting the hierarchy component). + // That way all children will have components created before the parent creates its own + std::vector jEntities = j["entities"]; + std::vector entities; + for (auto jEntity : jEntities) { + auto entity = scene->CreateEntity(); + EntityFromJson(jEntity, entity, scene); + entities.push_back(entity); + } + + auto& comp = p.AddComponent(); + comp.root = j["root"]; + for (auto entity : entities) { + comp.AddChild(entity); + } + } + } + +} \ No newline at end of file diff --git a/src/engine/scene/EntitySerializer.h b/src/engine/scene/EntitySerializer.h index 62ca4bac4..1ee5ee47c 100644 --- a/src/engine/scene/EntitySerializer.h +++ b/src/engine/scene/EntitySerializer.h @@ -8,112 +8,7 @@ namespace Atlas::Scene { void EntityToJson(json& j, const Entity& p, Ref& scene, - std::set& insertedEntites) { - - AE_ASSERT(!insertedEntites.contains(p) && "Entity should only be present once in the hierarchy"); - - if (!insertedEntites.contains(p)) - insertedEntites.insert(p); - - j["id"] = size_t(p); - - if (p.HasComponent()) { - j["name"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["transform"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["mesh"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["light"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["camera"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["audio"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["audioVolume"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["rigidBody"] = p.GetComponent(); - } - if (p.HasComponent()) { - j["player"] = p.GetComponent(); - } - if (p.HasComponent()) { - // Need to check how to get this to work - auto& hierarchyComponent = p.GetComponent(); - std::vector entities; - for (auto entity : hierarchyComponent.GetChildren()) { - entities.emplace_back(); - EntityToJson(entities.back(), entity, scene, insertedEntites); - } - j["entities"] = entities; - j["root"] = hierarchyComponent.root; - } - } - - void EntityFromJson(const json& j, Entity& p, Ref& scene) { - size_t id = j["id"]; - - if (j.contains("name")) { - NameComponent comp = j["name"]; - p.AddComponent(comp); - } - if (j.contains("transform")) { - TransformComponent comp = j["transform"]; - p.AddComponent(comp); - } - if (j.contains("mesh")) { - MeshComponent comp = j["mesh"]; - p.AddComponent(std::move(comp)); - } - if (j.contains("light")) { - LightComponent comp = j["light"]; - p.AddComponent(comp); - } - if (j.contains("camera")) { - CameraComponent comp = j["camera"]; - p.AddComponent(comp); - } - if (j.contains("audio")) { - AudioComponent comp = j["audio"]; - p.AddComponent(comp); - } - if (j.contains("audioVolume")) { - AudioVolumeComponent comp = j["audioVolume"]; - p.AddComponent(comp); - } - if (j.contains("rigidBody")) { - RigidBodyComponent comp = j["rigidBody"]; - p.AddComponent(comp); - } - if (j.contains("player")) { - PlayerComponent comp = j["player"]; - p.AddComponent(comp); - } - if (j.contains("entities")) { - // We need to first push back to a temporary vector to not invalidate - // component references (i.e. when directly getting the hierarchy component). - // That way all children will have components created before the parent creates its own - std::vector jEntities = j["entities"]; - std::vector entities; - for (auto jEntity : jEntities) { - auto entity = scene->CreateEntity(); - EntityFromJson(jEntity, entity, scene); - entities.push_back(entity); - } - - auto& comp = p.AddComponent(); - comp.root = j["root"]; - for (auto entity : entities) { - comp.AddChild(entity); - } - } - } + std::set& insertedEntites); + void EntityFromJson(const json& j, Entity& p, Ref& scene); } \ No newline at end of file diff --git a/src/engine/scene/Scene.cpp b/src/engine/scene/Scene.cpp index 71ff08e7c..6e03a34ce 100644 --- a/src/engine/scene/Scene.cpp +++ b/src/engine/scene/Scene.cpp @@ -85,6 +85,24 @@ namespace Atlas { } + std::string NumberToString(auto number) { + + auto str = std::to_string(number); + auto pos = str.find("."); + if (pos != std::string::npos) + return str.substr(0, pos + 4); + return str; + + } + + std::string VecToString(auto vector) { + + return NumberToString(vector.x) + ", " + + NumberToString(vector.y) + ", " + + NumberToString(vector.z); + + } + void Scene::Timestep(float deltaTime) { this->deltaTime = deltaTime; @@ -214,10 +232,13 @@ namespace Atlas { auto meshSubset = entityManager.GetSubset(); for (auto entity : meshSubset) { auto& meshComponent = entityManager.Get(entity); - if (!meshComponent.mesh.IsLoaded()) + auto& transformComponent = entityManager.Get(entity); + if (!meshComponent.mesh.IsLoaded()) { + // We can't update the transform yet + transformComponent.updated = false; continue; + } - auto& transformComponent = entityManager.Get(entity); if (!transformComponent.changed && meshComponent.inserted) continue; @@ -234,8 +255,10 @@ namespace Atlas { for (auto entity : transformSubset) { auto& transformComponent = entityManager.Get(entity); - transformComponent.changed = false; - transformComponent.updated = false; + if (transformComponent.updated) { + transformComponent.changed = false; + transformComponent.updated = false; + } } // We also need to reset the hierarchy components as well @@ -246,6 +269,8 @@ namespace Atlas { hierarchyComponent.updated = false; } + // Everything below assumes that entities themselves have a transform + // Without it they won't be transformed when they are in a hierarchy auto cameraSubset = entityManager.GetSubset(); for (auto entity : cameraSubset) { const auto& [cameraComponent, transformComponent] = cameraSubset.Get(entity); @@ -253,6 +278,13 @@ namespace Atlas { cameraComponent.parentTransform = transformComponent.globalMatrix; } + auto textSubset = entityManager.GetSubset(); + for (auto entity : textSubset) { + const auto& [textComponent, transformComponent] = textSubset.Get(entity); + + textComponent.Update(transformComponent); + } + auto lightSubset = entityManager.GetSubset(); for (auto entity : lightSubset) { auto& lightComponent = lightSubset.Get(entity); @@ -396,6 +428,76 @@ namespace Atlas { } + Volume::RayResult Scene::CastRay(Volume::Ray& ray, SceneQueryComponents queryComponents) { + + Volume::RayResult result; + + // Most accurate method if it works + if (physicsWorld && (queryComponents & SceneQueryComponentBits::RigidBodyComponentBit)) { + auto bodyResult = physicsWorld->CastRay(ray); + + if (bodyResult.valid) { + auto userData = bodyResult.data.GetUserData(); + + result.valid = true; + result.hitDistance = bodyResult.hitDistance; + result.normal = bodyResult.normal; + result.data = { userData, &entityManager }; + } + } + + // This isn't really optimized, we could use hierarchical data structures + if (queryComponents & SceneQueryComponentBits::MeshComponentBit) { + auto meshSubset = entityManager.GetSubset(); + + for (auto entity : meshSubset) { + const auto& meshComp = meshSubset.Get(entity); + + auto dist = 0.0f; + if (ray.Intersects(meshComp.aabb, 0.0f, result.hitDistance, dist)) { + auto rigidBody = entityManager.TryGet(entity); + // This means we already found a more accurate hit + if (result.valid && result.data != entity && + entityManager.Contains(entity)) + continue; + + // Accept all hits greater equal if they were within the updated hit distance + if (dist > 0.0f) { + result.valid = true; + result.data = { entity, &entityManager }; + result.hitDistance = dist; + } + + // Only accept zero hits (i.e we're inside their volume) if there wasn't anything before + if (!result.valid) { + result.valid = true; + result.data = { entity, &entityManager }; + } + } + } + } + + if (queryComponents & SceneQueryComponentBits::TextComponentBit) { + auto textSubset = entityManager.GetSubset(); + + for (auto entity : textSubset) { + const auto& textComp = textSubset.Get(entity); + + auto dist = 0.0f; + if (ray.Intersects(textComp.GetRectangle(), 0.0f, result.hitDistance, dist)) { + + result.valid = true; + result.data = { entity, &entityManager }; + result.hitDistance = dist; + + } + } + } + + return result; + + } + void Scene::GetRenderList(Volume::Frustum frustum, Atlas::RenderList &renderList) { // This is much quicker presumably due to cache coherency (need better hierarchical data structure) @@ -656,15 +758,15 @@ namespace Atlas { // Normal components without resources which are not a hierarchy that needs special treatment if (srcEntity.HasComponent()) { - auto& otherComp = srcEntity.GetComponent(); + const auto& otherComp = srcEntity.GetComponent(); dstEntity.AddComponent(otherComp); } if (srcEntity.HasComponent()) { - auto& otherComp = srcEntity.GetComponent(); + const auto& otherComp = srcEntity.GetComponent(); dstEntity.AddComponent(otherComp); } if (srcEntity.HasComponent()) { - auto& otherComp = srcEntity.GetComponent(); + const auto& otherComp = srcEntity.GetComponent(); auto& comp = dstEntity.AddComponent(otherComp); // Set isMain to false by default comp.isMain = false; @@ -672,8 +774,8 @@ namespace Atlas { if (srcEntity.HasComponent()) { auto otherComp = srcEntity.GetComponent(); auto& comp = dstEntity.AddComponent(otherComp); - // Need to copy the reference as well, create new textures afterwards with SetResolution - *comp.shadow = *otherComp.shadow; + // Need to create a new shadow, since right now the memory is shared between components + comp.shadow = CreateRef(*otherComp.shadow); comp.shadow->SetResolution(comp.shadow->resolution); comp.isMain = false; } @@ -681,6 +783,12 @@ namespace Atlas { auto otherComp = srcEntity.GetComponent(); dstEntity.AddComponent(otherComp.GetBodyCreationSettings()); } + if (srcEntity.HasComponent()) { + auto otherComp = srcEntity.GetComponent(); + auto& comp = dstEntity.AddComponent(otherComp); + // Need to create a new creation settings, since right now the memory is shared between components + comp.creationSettings = CreateRef(*otherComp.creationSettings); + } // Resource components need extra attention (resources need to be registered in this scene) // We can do a straight copy afterwards, to get around copying every field @@ -690,13 +798,18 @@ namespace Atlas { comp = otherComp; } if (srcEntity.HasComponent()) { + // These have a proper copy constructor auto otherComp = srcEntity.GetComponent(); - auto& comp = dstEntity.AddComponent(otherComp.stream->data); - comp = otherComp; + dstEntity.AddComponent(otherComp); } if (srcEntity.HasComponent()) { + // These have a proper copy constructor auto otherComp = srcEntity.GetComponent(); - auto& comp = dstEntity.AddComponent(otherComp.stream->data, otherComp.aabb); + dstEntity.AddComponent(otherComp); + } + if (srcEntity.HasComponent()) { + auto otherComp = srcEntity.GetComponent(); + auto& comp = dstEntity.AddComponent(otherComp.font, otherComp.text); comp = otherComp; } diff --git a/src/engine/scene/Scene.h b/src/engine/scene/Scene.h index b8c80126e..29ffd9e57 100644 --- a/src/engine/scene/Scene.h +++ b/src/engine/scene/Scene.h @@ -37,6 +37,15 @@ namespace Atlas { namespace Scene { + typedef uint32_t SceneQueryComponents; + + typedef enum SceneQueryComponentBits { + MeshComponentBit = (1 << 0), + RigidBodyComponentBit = (1 << 1), + TextComponentBit = (1 << 2), + AllComponentsBit = (1 << 3) - 1 + } SceneQueryComponentBits; + class Scene : public SpacePartitioning { template @@ -87,6 +96,9 @@ namespace Atlas { bool HasMainCamera() const; + Volume::RayResult CastRay(Volume::Ray& ray, + SceneQueryComponents queryComponents = SceneQueryComponentBits::AllComponentsBit); + void GetRenderList(Volume::Frustum frustum, RenderList& renderList); void ClearRTStructures(); diff --git a/src/engine/scene/components/AudioComponent.cpp b/src/engine/scene/components/AudioComponent.cpp index 261ec1edd..a0b52962e 100644 --- a/src/engine/scene/components/AudioComponent.cpp +++ b/src/engine/scene/components/AudioComponent.cpp @@ -9,6 +9,20 @@ namespace Atlas { namespace Components { + AudioComponent::AudioComponent(Scene *scene, const AudioComponent &that) { + + if (this != &that) { + *this = that; + // Need to create new stream + if (stream) { + stream = Audio::AudioManager::CreateStream(stream->data, 0.0f); + } + } + + this->scene = scene; + + } + AudioComponent::AudioComponent(Scene* scene, ResourceHandle audioData, float falloffFactor, bool loop) : falloffFactor(falloffFactor), scene(scene) { @@ -50,7 +64,16 @@ namespace Atlas { auto distance = glm::max(epsilon, glm::distance(objectLocation, listenerLocation)); - float distanceVolume = glm::min(1.0f, falloffFactor / distance); + // Use quick paths for "normal" powers and do nothing for the power=1 case + auto powerDistance = distance; + if (falloffPower == 2.0f) + powerDistance *= powerDistance; + else if (falloffPower == 3.0f) + powerDistance *= powerDistance * powerDistance; + else if (falloffPower != 1.0f) + powerDistance = powf(powerDistance, falloffPower); + + float distanceVolume = glm::min(1.0f, falloffFactor / powerDistance); auto audible = distanceVolume > cutoff; diff --git a/src/engine/scene/components/AudioComponent.h b/src/engine/scene/components/AudioComponent.h index 1345aa5b2..449bed22c 100644 --- a/src/engine/scene/components/AudioComponent.h +++ b/src/engine/scene/components/AudioComponent.h @@ -20,13 +20,15 @@ namespace Atlas { public: AudioComponent() = default; explicit AudioComponent(Scene* scene) : scene(scene) {} + explicit AudioComponent(Scene* scene, const AudioComponent& that); explicit AudioComponent(Scene* scene, ResourceHandle audioData, float falloffFactor = 1.0f, bool loop = false); void ChangeResource(ResourceHandle audioData); - float falloffFactor = 1.0f; - float cutoff = 0.0001f; + float falloffFactor = 0.5f; + float falloffPower = 2.0f; + float cutoff = 0.001f; float volume = 1.0f; diff --git a/src/engine/scene/components/AudioVolumeComponent.cpp b/src/engine/scene/components/AudioVolumeComponent.cpp index 3d137dae6..e75fd7a46 100644 --- a/src/engine/scene/components/AudioVolumeComponent.cpp +++ b/src/engine/scene/components/AudioVolumeComponent.cpp @@ -14,6 +14,10 @@ namespace Atlas { if (this != &that) { *this = that; + // Need to create new stream + if (stream) { + stream = Audio::AudioManager::CreateStream(stream->data, 0.0f); + } } this->scene = scene; @@ -63,8 +67,17 @@ namespace Atlas { transformedAABB = aabb.Transform(transformComponent.globalMatrix); auto distance = glm::max(epsilon, transformedAABB.GetDistance(listenerLocation)); - float distanceVolume = glm::min(1.0f, falloffFactor / distance); + // Use quick paths for "normal" powers and do nothing for the power=1 case + auto powerDistance = distance; + if (falloffPower == 2.0f) + powerDistance *= powerDistance; + else if (falloffPower == 3.0f) + powerDistance *= powerDistance * powerDistance; + else if (falloffPower != 1.0f) + powerDistance = powf(powerDistance, falloffPower); + + float distanceVolume = glm::min(1.0f, falloffFactor / powerDistance); auto audible = distanceVolume > cutoff; if (audible) { diff --git a/src/engine/scene/components/AudioVolumeComponent.h b/src/engine/scene/components/AudioVolumeComponent.h index 0b0551241..2e8fba53b 100644 --- a/src/engine/scene/components/AudioVolumeComponent.h +++ b/src/engine/scene/components/AudioVolumeComponent.h @@ -28,7 +28,8 @@ namespace Atlas { Volume::AABB GetTransformedAABB() const; - float falloffFactor = 1.0f; + float falloffFactor = 0.5f; + float falloffPower = 2.0f; float cutoff = 0.0001f; float volume = 1.0f; diff --git a/src/engine/scene/components/ComponentSerializer.cpp b/src/engine/scene/components/ComponentSerializer.cpp new file mode 100644 index 000000000..d4a5f0851 --- /dev/null +++ b/src/engine/scene/components/ComponentSerializer.cpp @@ -0,0 +1,284 @@ +#include "ComponentSerializer.h" + +namespace Atlas::Scene::Components { + + void to_json(json& j, const AudioComponent& p) { + j = json { + {"falloffFactor", p.falloffFactor}, + {"falloffPower", p.falloffPower}, + {"cutoff", p.cutoff}, + {"volume", p.volume}, + }; + + if (p.stream) { + j["stream"] = *p.stream; + } + } + + void from_json(const json& j, AudioComponent& p) { + p.stream = Audio::AudioManager::CreateStream(ResourceHandle()); + + j.at("falloffFactor").get_to(p.falloffFactor); + j.at("falloffPower").get_to(p.falloffPower); + j.at("cutoff").get_to(p.cutoff); + j.at("volume").get_to(p.volume); + + if (j.contains("stream")) { + j.at("stream").get_to(*p.stream); + } + } + + void to_json(json& j, const AudioVolumeComponent& p) { + j = json { + {"falloffFactor", p.falloffFactor}, + {"falloffPower", p.falloffPower}, + {"cutoff", p.cutoff}, + {"volume", p.volume}, + {"aabb", p.aabb} + }; + + if (p.stream) { + j["stream"] = *p.stream; + } + } + + void from_json(const json& j, AudioVolumeComponent& p) { + p.stream = Audio::AudioManager::CreateStream(ResourceHandle()); + + j.at("falloffFactor").get_to(p.falloffFactor); + j.at("falloffPower").get_to(p.falloffPower); + j.at("cutoff").get_to(p.cutoff); + j.at("aabb").get_to(p.aabb); + j.at("volume").get_to(p.volume); + + if (j.contains("stream")) { + j.at("stream").get_to(*p.stream); + } + } + + void to_json(json& j, const CameraComponent& p) { + j = json { + {"location", p.location}, + {"rotation", p.rotation}, + {"exposure", p.exposure}, + {"fieldOfView", p.fieldOfView}, + {"aspectRatio", p.aspectRatio}, + {"nearPlane", p.nearPlane}, + {"farPlane", p.farPlane}, + {"thirdPerson", p.thirdPerson}, + {"thirdPersonDistance", p.thirdPersonDistance}, + {"isMain", p.isMain}, + {"useEntityTranslation", p.useEntityTranslation}, + {"useEntityRotation", p.useEntityRotation} + }; + } + + void from_json(const json& j, CameraComponent& p) { + j.at("location").get_to(p.location); + j.at("rotation").get_to(p.rotation); + j.at("exposure").get_to(p.exposure); + j.at("fieldOfView").get_to(p.fieldOfView); + j.at("aspectRatio").get_to(p.aspectRatio); + j.at("nearPlane").get_to(p.nearPlane); + j.at("farPlane").get_to(p.farPlane); + j.at("thirdPerson").get_to(p.thirdPerson); + j.at("thirdPersonDistance").get_to(p.thirdPersonDistance); + j.at("isMain").get_to(p.isMain); + j.at("useEntityTranslation").get_to(p.useEntityTranslation); + j.at("useEntityRotation").get_to(p.useEntityRotation); + } + + void to_json(json& j, const LightComponent& p) { + json typeProperties; + if (p.type == LightType::DirectionalLight) { + typeProperties = json { + {"direction", p.properties.directional.direction} + }; + } + else if (p.type == LightType::PointLight) { + typeProperties = json { + {"position", p.properties.point.position}, + {"radius", p.properties.point.radius}, + {"attenuation", p.properties.point.attenuation}, + }; + } + + int type = static_cast(p.type); + int mobility = static_cast(p.mobility); + + j = json { + {"type", type}, + {"mobility", mobility}, + {"color", p.color}, + {"intensity", p.intensity}, + {"properties", typeProperties}, + {"shadow", *p.shadow}, + {"isMain", p.isMain}, + {"volumetric", p.volumetric} + }; + } + + void from_json(const json& j, LightComponent& p) { + json typeProperties; + int type, mobility; + + p.shadow = CreateRef(); + + j.at("type").get_to(type); + j.at("mobility").get_to(mobility); + j.at("color").get_to(p.color); + j.at("intensity").get_to(p.intensity); + j.at("properties").get_to(typeProperties); + j.at("shadow").get_to(*p.shadow); + j.at("isMain").get_to(p.isMain); + j.at("volumetric").get_to(p.volumetric); + + p.type = static_cast(type); + p.mobility = static_cast(type); + + if (p.type == LightType::DirectionalLight) { + typeProperties.at("direction").get_to(p.properties.directional.direction); + } + else if (p.type == LightType::PointLight) { + typeProperties.at("position").get_to(p.properties.point.position); + typeProperties.at("radius").get_to(p.properties.point.radius); + typeProperties.at("attenuation").get_to(p.properties.point.attenuation); + } + } + + void to_json(json& j, const MeshComponent& p) { + j = json { + {"visible", p.visible}, + {"dontCull", p.dontCull} + }; + + if (p.mesh.IsValid()) + j["resourcePath"] = p.mesh.GetResource()->path; + } + + void from_json(const json& j, MeshComponent& p) { + j.at("visible").get_to(p.visible); + j.at("dontCull").get_to(p.dontCull); + + if (j.contains("resourcePath")) { + std::string resourcePath; + j.at("resourcePath").get_to(resourcePath); + + p.mesh = ResourceManager::GetOrLoadResourceWithLoaderAsync(resourcePath, + ResourceOrigin::User, Loader::ModelLoader::LoadMesh, false, 8192); + } + + } + + void to_json(json& j, const NameComponent& p) { + j = json { + {"name", p.name}, + }; + } + + void from_json(const json& j, NameComponent& p) { + j.at("name").get_to(p.name); + } + + void to_json(json& j, const TransformComponent& p) { + j = json { + {"matrix", p.matrix}, + {"isStatic", p.isStatic}, + }; + } + + void from_json(const json& j, TransformComponent& p) { + j.at("matrix").get_to(p.matrix); + j.at("isStatic").get_to(p.isStatic); + } + + void to_json(json& j, const TextComponent& p) { + j = json { + {"text", p.text}, + {"position", p.position}, + {"rotation", p.rotation}, + {"halfSize", p.halfSize}, + {"textColor", p.textColor}, + {"outlineColor", p.outlineColor}, + {"outlineFactor", p.outlineFactor}, + {"textScale", p.textScale}, + }; + + if (p.font.IsValid()) + j["resourcePath"] = p.font.GetResource()->path; + } + + void from_json(const json& j, TextComponent& p) { + j.at("text").get_to(p.text); + j.at("position").get_to(p.position); + j.at("rotation").get_to(p.rotation); + j.at("halfSize").get_to(p.halfSize); + j.at("textColor").get_to(p.textColor); + j.at("outlineColor").get_to(p.outlineColor); + j.at("outlineFactor").get_to(p.outlineFactor); + j.at("textScale").get_to(p.textScale); + + if (j.contains("resourcePath")) { + std::string resourcePath; + j.at("resourcePath").get_to(resourcePath); + + p.font = ResourceManager::GetOrLoadResourceAsync(resourcePath, + ResourceOrigin::User, 32.0f, 7.0f, 127); + } + } + + void to_json(json& j, const RigidBodyComponent& p) { + j = json { + {"bodyIndex", p.bodyId.GetIndex()}, + {"layer", p.layer}, + }; + + // This is only relevant for not yet created bodies + if (p.creationSettings != nullptr) + j["creationSettings"] = *p.creationSettings; + } + + void from_json(const json& j, RigidBodyComponent& p) { + // This whole thing works because only components with physics world are considered valid, + // so the body id will be overriden at some point. + uint32_t bodyIndex; + j.at("bodyIndex").get_to(bodyIndex); + j.at("layer").get_to(p.layer); + + // This is only relevant for not yet created bodies + if (j.contains("creationSettings")) { + p.creationSettings = CreateRef(); + *p.creationSettings = j["creationSettings"]; + } + + // We can use the index here since when loading nothing but the loading thread + // will access the physics system (in multithreaded scenarios we would need to use + // the sequence number as well + p.bodyId = Physics::BodyID(bodyIndex); + } + + void to_json(json& j, const PlayerComponent& p) { + j = json { + {"slowVelocity", p.slowVelocity}, + {"fastVelocity", p.fastVelocity}, + {"jumpVelocity", p.jumpVelocity}, + {"allowInput", p.allowInput}, + }; + + if (p.creationSettings != nullptr) + j["creationSettings"] = *p.creationSettings; + } + + void from_json(const json& j, PlayerComponent& p) { + j.at("slowVelocity").get_to(p.slowVelocity); + j.at("fastVelocity").get_to(p.fastVelocity); + j.at("jumpVelocity").get_to(p.jumpVelocity); + j.at("allowInput").get_to(p.allowInput); + + if (j.contains("creationSettings")) { + p.creationSettings = CreateRef(); + *p.creationSettings = j["creationSettings"]; + } + } + +} \ No newline at end of file diff --git a/src/engine/scene/components/ComponentSerializer.h b/src/engine/scene/components/ComponentSerializer.h index a9c2ae646..c9e27ae81 100644 --- a/src/engine/scene/components/ComponentSerializer.h +++ b/src/engine/scene/components/ComponentSerializer.h @@ -12,242 +12,44 @@ namespace Atlas::Scene::Components { - void to_json(json& j, const AudioComponent& p) { - j = json { - {"falloffFactor", p.falloffFactor}, - {"cutoff", p.cutoff}, - {"volume", p.volume}, - }; - - if (p.stream) { - j["stream"] = *p.stream; - } - } - - void from_json(const json& j, AudioComponent& p) { - p.stream = Audio::AudioManager::CreateStream(ResourceHandle()); - - j.at("falloffFactor").get_to(p.falloffFactor); - j.at("cutoff").get_to(p.cutoff); - j.at("volume").get_to(p.volume); - - if (j.contains("stream")) { - j.at("stream").get_to(*p.stream); - } - } - - void to_json(json& j, const AudioVolumeComponent& p) { - j = json { - {"falloffFactor", p.falloffFactor}, - {"cutoff", p.cutoff}, - {"volume", p.volume}, - {"aabb", p.aabb} - }; - - if (p.stream) { - j["stream"] = *p.stream; - } - } - - void from_json(const json& j, AudioVolumeComponent& p) { - p.stream = Audio::AudioManager::CreateStream(ResourceHandle()); - - j.at("falloffFactor").get_to(p.falloffFactor); - j.at("cutoff").get_to(p.cutoff); - j.at("aabb").get_to(p.aabb); - j.at("volume").get_to(p.volume); - - if (j.contains("stream")) { - j.at("stream").get_to(*p.stream); - } - } - - void to_json(json& j, const CameraComponent& p) { - j = json { - {"location", p.location}, - {"rotation", p.rotation}, - {"exposure", p.exposure}, - {"fieldOfView", p.fieldOfView}, - {"aspectRatio", p.aspectRatio}, - {"nearPlane", p.nearPlane}, - {"farPlane", p.farPlane}, - {"thirdPerson", p.thirdPerson}, - {"thirdPersonDistance", p.thirdPersonDistance}, - {"isMain", p.isMain}, - {"useEntityTranslation", p.useEntityTranslation}, - {"useEntityRotation", p.useEntityRotation} - }; - } - - void from_json(const json& j, CameraComponent& p) { - j.at("location").get_to(p.location); - j.at("rotation").get_to(p.rotation); - j.at("exposure").get_to(p.exposure); - j.at("fieldOfView").get_to(p.fieldOfView); - j.at("aspectRatio").get_to(p.aspectRatio); - j.at("nearPlane").get_to(p.nearPlane); - j.at("farPlane").get_to(p.farPlane); - j.at("thirdPerson").get_to(p.thirdPerson); - j.at("thirdPersonDistance").get_to(p.thirdPersonDistance); - j.at("isMain").get_to(p.isMain); - j.at("useEntityTranslation").get_to(p.useEntityTranslation); - j.at("useEntityRotation").get_to(p.useEntityRotation); - } - - void to_json(json& j, const LightComponent& p) { - json typeProperties; - if (p.type == LightType::DirectionalLight) { - typeProperties = json { - {"direction", p.properties.directional.direction} - }; - } - else if (p.type == LightType::PointLight) { - typeProperties = json { - {"position", p.properties.point.position}, - {"radius", p.properties.point.radius}, - {"attenuation", p.properties.point.attenuation}, - }; - } - - int type = static_cast(p.type); - int mobility = static_cast(p.mobility); - - j = json { - {"type", type}, - {"mobility", mobility}, - {"color", p.color}, - {"intensity", p.intensity}, - {"properties", typeProperties}, - {"shadow", *p.shadow}, - {"isMain", p.isMain}, - {"volumetric", p.volumetric} - }; - } - - void from_json(const json& j, LightComponent& p) { - json typeProperties; - int type, mobility; - - p.shadow = CreateRef(); - - j.at("type").get_to(type); - j.at("mobility").get_to(mobility); - j.at("color").get_to(p.color); - j.at("intensity").get_to(p.intensity); - j.at("properties").get_to(typeProperties); - j.at("shadow").get_to(*p.shadow); - j.at("isMain").get_to(p.isMain); - j.at("volumetric").get_to(p.volumetric); - - p.type = static_cast(type); - p.mobility = static_cast(type); - - if (p.type == LightType::DirectionalLight) { - typeProperties.at("direction").get_to(p.properties.directional.direction); - } - else if (p.type == LightType::PointLight) { - typeProperties.at("position").get_to(p.properties.point.position); - typeProperties.at("radius").get_to(p.properties.point.radius); - typeProperties.at("attenuation").get_to(p.properties.point.attenuation); - } - } - - void to_json(json& j, const MeshComponent& p) { - j = json { - {"visible", p.visible}, - {"dontCull", p.dontCull} - }; - - if (p.mesh.IsValid()) - j["resourcePath"] = p.mesh.GetResource()->path; - } - - void from_json(const json& j, MeshComponent& p) { - j.at("visible").get_to(p.visible); - j.at("dontCull").get_to(p.dontCull); - - if (j.contains("resourcePath")) { - std::string resourcePath; - j.at("resourcePath").get_to(resourcePath); - - p.mesh = ResourceManager::GetOrLoadResourceWithLoaderAsync(resourcePath, - ResourceOrigin::User, Loader::ModelLoader::LoadMesh, false, 8192); - } - - } - - void to_json(json& j, const NameComponent& p) { - j = json { - {"name", p.name}, - }; - } - - void from_json(const json& j, NameComponent& p) { - j.at("name").get_to(p.name); - } - - void to_json(json& j, const TransformComponent& p) { - j = json { - {"matrix", p.matrix}, - {"isStatic", p.isStatic}, - }; - } - - void from_json(const json& j, TransformComponent& p) { - j.at("matrix").get_to(p.matrix); - j.at("isStatic").get_to(p.isStatic); - } - - void to_json(json& j, const RigidBodyComponent& p) { - j = json { - {"bodyIndex", p.bodyId.GetIndex()}, - {"layer", p.layer}, - }; - - // This is only relevant for not yet created bodies - if (p.creationSettings != nullptr) - j["creationSettings"] = *p.creationSettings; - } - - void from_json(const json& j, RigidBodyComponent& p) { - // This whole thing works because only components with physics world are considered valid, - // so the body id will be overriden at some point. - uint32_t bodyIndex; - j.at("bodyIndex").get_to(bodyIndex); - j.at("layer").get_to(p.layer); - - // This is only relevant for not yet created bodies - if (j.contains("creationSettings")) { - p.creationSettings = CreateRef(); - *p.creationSettings = j["creationSettings"]; - } - - // We can use the index here since when loading nothing but the loading thread - // will access the physics system (in multithreaded scenarios we would need to use - // the sequence number as well - p.bodyId = Physics::BodyID(bodyIndex); - } - - void to_json(json& j, const PlayerComponent& p) { - j = json { - {"slowVelocity", p.slowVelocity}, - {"fastVelocity", p.fastVelocity}, - {"jumpVelocity", p.jumpVelocity}, - }; - - if (p.creationSettings != nullptr) - j["creationSettings"] = *p.creationSettings; - } - - void from_json(const json& j, PlayerComponent& p) { - j.at("slowVelocity").get_to(p.slowVelocity); - j.at("fastVelocity").get_to(p.fastVelocity); - j.at("jumpVelocity").get_to(p.jumpVelocity); - - if (j.contains("creationSettings")) { - p.creationSettings = CreateRef(); - *p.creationSettings = j["creationSettings"]; - } - } + void to_json(json& j, const AudioComponent& p); + + void from_json(const json& j, AudioComponent& p); + + void to_json(json& j, const AudioVolumeComponent& p); + + void from_json(const json& j, AudioVolumeComponent& p); + + void to_json(json& j, const CameraComponent& p); + + void from_json(const json& j, CameraComponent& p); + + void to_json(json& j, const LightComponent& p); + + void from_json(const json& j, LightComponent& p); + + void to_json(json& j, const MeshComponent& p); + + void from_json(const json& j, MeshComponent& p); + + void to_json(json& j, const NameComponent& p); + + void from_json(const json& j, NameComponent& p); + + void to_json(json& j, const TransformComponent& p); + + void from_json(const json& j, TransformComponent& p); + + void to_json(json& j, const TextComponent& p); + + void from_json(const json& j, TextComponent& p); + + void to_json(json& j, const RigidBodyComponent& p); + + void from_json(const json& j, RigidBodyComponent& p); + + void to_json(json& j, const PlayerComponent& p); + + void from_json(const json& j, PlayerComponent& p); } \ No newline at end of file diff --git a/src/engine/scene/components/Components.h b/src/engine/scene/components/Components.h index e250ecab8..c623c8546 100644 --- a/src/engine/scene/components/Components.h +++ b/src/engine/scene/components/Components.h @@ -10,4 +10,5 @@ #include "TransformComponent.h" #include "ObjectComponent.h" #include "RigidBodyComponent.h" -#include "PlayerComponent.h" \ No newline at end of file +#include "PlayerComponent.h" +#include "TextComponent.h" \ No newline at end of file diff --git a/src/engine/scene/components/LightComponent.cpp b/src/engine/scene/components/LightComponent.cpp index 0f535212b..edf648eca 100644 --- a/src/engine/scene/components/LightComponent.cpp +++ b/src/engine/scene/components/LightComponent.cpp @@ -41,7 +41,7 @@ namespace Atlas { } void LightComponent::AddDirectionalShadow(float distance, float bias, int32_t resolution, - vec3 shadowCenter, mat4 orthoProjection) { + vec3 shadowCenter, vec4 orthoSize) { AE_ASSERT(type == LightType::DirectionalLight && "Component must be of type directional light"); @@ -51,6 +51,9 @@ namespace Atlas { shadow->allowTerrain = true; + auto orthoProjection = glm::ortho(orthoSize.x, orthoSize.y, orthoSize.z, orthoSize.w, -2000.0f, 2000.0f); + + shadow->views[0].orthoSize = orthoSize; shadow->views[0].nearDistance = 0.0f; shadow->views[0].farDistance = distance; shadow->views[0].projectionMatrix = clipMatrix * orthoProjection; diff --git a/src/engine/scene/components/LightComponent.h b/src/engine/scene/components/LightComponent.h index a328816f2..1d0cd9a38 100644 --- a/src/engine/scene/components/LightComponent.h +++ b/src/engine/scene/components/LightComponent.h @@ -65,7 +65,7 @@ namespace Atlas { int32_t cascadeCount, float splitCorrection, bool longRange = false, float longRangeDistance = 0.0f); void AddDirectionalShadow(float distance, float bias, int32_t resolution, - vec3 shadowCenter, mat4 orthoProjection); + vec3 shadowCenter, vec4 orthoSize); void AddPointShadow(float bias, int32_t resolution); diff --git a/src/engine/scene/components/MeshComponent.cpp b/src/engine/scene/components/MeshComponent.cpp index e7bd82064..7c028c258 100644 --- a/src/engine/scene/components/MeshComponent.cpp +++ b/src/engine/scene/components/MeshComponent.cpp @@ -7,7 +7,7 @@ namespace Atlas { namespace Components { - void MeshComponent::ChangeResource(ResourceHandle mesh) { + void MeshComponent::ChangeResource(const ResourceHandle& mesh) { AE_ASSERT(scene != nullptr && "Component needs to be added to entity before changing mesh"); diff --git a/src/engine/scene/components/MeshComponent.h b/src/engine/scene/components/MeshComponent.h index 28489a184..cf14cc3bf 100644 --- a/src/engine/scene/components/MeshComponent.h +++ b/src/engine/scene/components/MeshComponent.h @@ -23,7 +23,7 @@ namespace Atlas { explicit MeshComponent(Scene* scene, ResourceHandle mesh) : scene(scene), mesh(mesh) {}; - void ChangeResource(ResourceHandle mesh); + void ChangeResource(const ResourceHandle& mesh); ResourceHandle mesh = ResourceHandle(); diff --git a/src/engine/scene/components/PlayerComponent.cpp b/src/engine/scene/components/PlayerComponent.cpp index 2480e48b1..655c6bad8 100644 --- a/src/engine/scene/components/PlayerComponent.cpp +++ b/src/engine/scene/components/PlayerComponent.cpp @@ -33,17 +33,18 @@ namespace Atlas::Scene::Components { auto groundVelocity = GetGroundVelocity(); if (IsOnGround()) { newVelocity += groundVelocity; - if (jump) { + if (jump) newVelocity += up * jumpVelocity; - } - newVelocity += inputVelocity; + if (allowInput) + newVelocity += inputVelocity; } else { newVelocity += GetLinearVelocity(); // Add reduced input velocity such that jumping doesn't feel weird - newVelocity += inputVelocity * 0.01f; + if (allowInput) + newVelocity += inputVelocity * 0.01f; } - + jump = false; newVelocity += up * world->GetGravity() * deltaTime; diff --git a/src/engine/scene/components/PlayerComponent.h b/src/engine/scene/components/PlayerComponent.h index 0df224e57..fcf5363c6 100644 --- a/src/engine/scene/components/PlayerComponent.h +++ b/src/engine/scene/components/PlayerComponent.h @@ -30,6 +30,8 @@ namespace Atlas::Scene { float fastVelocity = 4.0f; float jumpVelocity = 4.0f; + bool allowInput = true; + private: void Update(float deltaTime); diff --git a/src/engine/scene/components/RigidBodyComponent.cpp b/src/engine/scene/components/RigidBodyComponent.cpp index bacfae4e1..5fb4b06a5 100644 --- a/src/engine/scene/components/RigidBodyComponent.cpp +++ b/src/engine/scene/components/RigidBodyComponent.cpp @@ -6,6 +6,16 @@ namespace Atlas { namespace Components { + RigidBodyComponent::RigidBodyComponent(Scene* scene, Entity entity, const RigidBodyComponent& that) { + + if (this != &that) { + *this = that; + } + + this->entity = entity; + + } + Physics::BodyCreationSettings RigidBodyComponent::GetBodyCreationSettings() { if (creationSettings) @@ -27,7 +37,7 @@ namespace Atlas { this->world = physicsWorld; - auto body = physicsWorld->CreateBody(*creationSettings, transformComponent.globalMatrix); + auto body = physicsWorld->CreateBody(*creationSettings, transformComponent.globalMatrix, entity); // Just copy the body id, fine afterwards bodyId = body.bodyId; diff --git a/src/engine/scene/components/RigidBodyComponent.h b/src/engine/scene/components/RigidBodyComponent.h index 4ed316afd..6f6a4ebd0 100644 --- a/src/engine/scene/components/RigidBodyComponent.h +++ b/src/engine/scene/components/RigidBodyComponent.h @@ -3,6 +3,7 @@ #include "../../System.h" #include "../../physics/PhysicsWorld.h" +#include "../Entity.h" #include "TransformComponent.h" namespace Atlas { @@ -17,9 +18,10 @@ namespace Atlas { public: RigidBodyComponent() = default; - RigidBodyComponent(const RigidBodyComponent& that) = default; - explicit RigidBodyComponent(const Physics::BodyCreationSettings& bodyCreationSettings) - : layer(bodyCreationSettings.objectLayer), creationSettings(CreateRef(bodyCreationSettings)) {} + RigidBodyComponent(Scene* scene, Entity entity) : entity(entity) {} + RigidBodyComponent(Scene* scene, Entity entity, const RigidBodyComponent& that); + explicit RigidBodyComponent(Scene* scene, Entity entity, const Physics::BodyCreationSettings& bodyCreationSettings) + : entity(entity), layer(bodyCreationSettings.objectLayer), creationSettings(CreateRef(bodyCreationSettings)) {} Physics::BodyCreationSettings GetBodyCreationSettings() override; @@ -32,6 +34,8 @@ namespace Atlas { void RemoveFromPhysicsWorld(); + ECS::Entity entity; + friend Scene; }; diff --git a/src/engine/scene/components/TextComponent.cpp b/src/engine/scene/components/TextComponent.cpp new file mode 100644 index 000000000..1954748fe --- /dev/null +++ b/src/engine/scene/components/TextComponent.cpp @@ -0,0 +1,32 @@ +#include "TextComponent.h" + +namespace Atlas::Scene::Components { + + void TextComponent::ChangeResource(const ResourceHandle& font) { + + this->font = font; + + } + + Volume::Rectangle TextComponent::GetRectangle() const { + + auto right = glm::mat3_cast(transformedRotation) * glm::vec3(1.0f, 0.0f, 0.0f); + auto down = glm::mat3_cast(transformedRotation) * glm::vec3(0.0f, -1.0f, 0.0f); + + right *= 2.0f * halfSize.x; + down *= 2.0f * halfSize.y; + + auto rectOrigin = transformedPosition - right * 0.5f - down * 0.5f; + + return Volume::Rectangle(rectOrigin, right, down); + + } + + void TextComponent::Update(const TransformComponent& transform) { + + transformedPosition = vec4(transform.globalMatrix * vec4(position, 1.0f)); + transformedRotation = glm::quat_cast(transform.globalMatrix) * rotation; + + } + +} \ No newline at end of file diff --git a/src/engine/scene/components/TextComponent.h b/src/engine/scene/components/TextComponent.h new file mode 100644 index 000000000..c848bcdd6 --- /dev/null +++ b/src/engine/scene/components/TextComponent.h @@ -0,0 +1,61 @@ +#pragma once + +#include "../../System.h" + +#include "TransformComponent.h" + +#include "../../Font.h" +#include "../../volume/Rectangle.h" +#include "../../resource/Resource.h" + +namespace Atlas::Scene { + + class Scene; + + namespace Components { + + class HierarchyComponent; + + class TextComponent { + + friend Scene; + + public: + TextComponent() = default; + TextComponent(const TextComponent& that) = default; + explicit TextComponent(const ResourceHandle& font, const std::string& text) : font(font), text(text) {} + + void ChangeResource(const ResourceHandle& font); + + Volume::Rectangle GetRectangle() const; + + vec3 GetTransformedPosition() const { return transformedPosition; } + quat GetTransformedRotation() const { return transformedRotation; } + + ResourceHandle font; + + std::string text; + + vec3 position = vec3(0.0f); + quat rotation = quat(); + + vec2 halfSize = vec2(1.0f); + + vec4 textColor = vec4(1.0f); + vec4 outlineColor = vec4(0.0f, 0.0f, 0.0f, 1.0f); + + float outlineFactor = 0.0f; + + float textScale = 1.0f; + + protected: + void Update(const TransformComponent& transform); + + vec3 transformedPosition; + quat transformedRotation; + + }; + + } + +} \ No newline at end of file diff --git a/src/engine/volume/AABB.cpp b/src/engine/volume/AABB.cpp index 78dc99a75..3ee56f037 100644 --- a/src/engine/volume/AABB.cpp +++ b/src/engine/volume/AABB.cpp @@ -40,23 +40,22 @@ namespace Atlas { vec3(max.x, max.y, min.z), vec3(max.x, max.y, max.z) }; for (uint8_t i = 0; i < 8; i++) { - auto homogeneous = matrix * vec4(cube[i], 1.0f); - cube[i] = vec3(homogeneous) / homogeneous.w; + cube[i] = matrix * vec4(cube[i], 1.0f); } - vec3 min = cube[0], max = cube[0]; + vec3 newMin = cube[0], newMax = cube[0]; for (uint8_t i = 1; i < 8; i++) { - min.x = glm::min(min.x, cube[i].x); - min.y = glm::min(min.y, cube[i].y); - min.z = glm::min(min.z, cube[i].z); + newMin.x = glm::min(newMin.x, cube[i].x); + newMin.y = glm::min(newMin.y, cube[i].y); + newMin.z = glm::min(newMin.z, cube[i].z); - max.x = glm::max(max.x, cube[i].x); - max.y = glm::max(max.y, cube[i].y); - max.z = glm::max(max.z, cube[i].z); + newMax.x = glm::max(newMax.x, cube[i].x); + newMax.y = glm::max(newMax.y, cube[i].y); + newMax.z = glm::max(newMax.z, cube[i].z); } - return AABB(min, max); + return AABB(newMin, newMax); } diff --git a/src/engine/volume/Ray.cpp b/src/engine/volume/Ray.cpp index b472ebbfb..86352995c 100644 --- a/src/engine/volume/Ray.cpp +++ b/src/engine/volume/Ray.cpp @@ -17,7 +17,7 @@ namespace Atlas { } - bool Ray::Intersects(AABB aabb, float tmin, float tmax) { + bool Ray::Intersects(const AABB& aabb, float tmin, float tmax) { auto t = 0.0f; @@ -25,7 +25,7 @@ namespace Atlas { } - bool Ray::Intersects(AABB aabb, float tmin, float tmax, float& t) { + bool Ray::Intersects(const AABB& aabb, float tmin, float tmax, float& t) { auto t0 = (aabb.min - origin) * inverseDirection; auto t1 = (aabb.max - origin) * inverseDirection; @@ -75,6 +75,34 @@ namespace Atlas { } + bool Ray::Intersects(const Rectangle& rect, float tmin, float tmax, float& t) { + + auto N = rect.GetNormal(); + + auto nDotD = glm::dot(N, direction); + if (nDotD >= 0.0f) + return false; + + float dist = glm::dot(rect.point - origin, N) / nDotD; + + if (dist <= tmin && dist > tmax) + return false; + + auto point = Get(dist) - rect.point; + + auto projS0 = glm::dot(rect.s0, point); + auto projS1 = glm::dot(rect.s1, point); + + if (projS0 >= 0.0f && projS0 <= glm::dot(rect.s0, rect.s0) && + projS1 >= 0.0f && projS1 <= glm::dot(rect.s1, rect.s1)) { + t = dist; + return true; + } + + return false; + + } + vec3 Ray::Distance(Ray ray, float& distance) { constexpr float minFloat = std::numeric_limits::min(); diff --git a/src/engine/volume/Ray.h b/src/engine/volume/Ray.h index 0758f05e2..6ccefa303 100644 --- a/src/engine/volume/Ray.h +++ b/src/engine/volume/Ray.h @@ -3,11 +3,22 @@ #include "../System.h" #include "AABB.h" +#include "Rectangle.h" namespace Atlas { namespace Volume { + template + struct RayResult { + bool IsNormalValid() const { return normal.x != 0.0f || normal.y != 0.0f || normal.z != 0.0f; } + + bool valid = false; + vec3 normal = vec3(0.0f); + float hitDistance = 10e12f; + T data; + }; + class Ray { public: @@ -17,14 +28,16 @@ namespace Atlas { vec3 Get(float distance) const; - bool Intersects(AABB aabb, float tmin, float tmax); + bool Intersects(const AABB& aabb, float tmin, float tmax); - bool Intersects(AABB aabb, float tmin, float tmax, float& t); + bool Intersects(const AABB& aabb, float tmin, float tmax, float& t); bool Intersects(vec3 v0, vec3 v1, vec3 v2); bool Intersects(vec3 v0, vec3 v1, vec3 v2, vec3& intersection); + bool Intersects(const Rectangle& rect, float tmin, float tmax, float& t); + vec3 Distance(Ray ray, float& distance); vec3 origin = vec3(0.0f); diff --git a/src/engine/volume/Rectangle.h b/src/engine/volume/Rectangle.h new file mode 100644 index 000000000..d0901ba4b --- /dev/null +++ b/src/engine/volume/Rectangle.h @@ -0,0 +1,20 @@ +#pragma once + +#include "System.h" + +namespace Atlas::Volume { + + class Rectangle { + + public: + Rectangle(vec3 point, vec3 s0, vec3 s1) : point(point), s0(s0), s1(s1) {} + + vec3 GetNormal() const { return glm::normalize(glm::cross(s1, s0)); } + + vec3 point; + vec3 s0; + vec3 s1; + + }; + +} \ No newline at end of file diff --git a/src/tests/App.cpp b/src/tests/App.cpp index 0204628cf..c30cf606c 100644 --- a/src/tests/App.cpp +++ b/src/tests/App.cpp @@ -61,8 +61,7 @@ void App::LoadContent(AppConfiguration config) { directionalLight.properties.directional.direction = glm::vec3(0.0f, -1.0f, 0.33f); directionalLight.color = glm::vec3(255, 236, 209) / 255.0f; - glm::mat4 orthoProjection = glm::ortho(-100.0f, 100.0f, -70.0f, 120.0f, -120.0f, 120.0f); - directionalLight.AddDirectionalShadow(200.0f, 3.0f, 4096, glm::vec3(0.0f), orthoProjection); + directionalLight.AddDirectionalShadow(200.0f, 3.0f, 4096, glm::vec3(0.0f), glm::vec4(-100.0f, 100.0f, -70.0f, 120.0f)); directionalLight.isMain = true; scene->ao = Atlas::CreateRef(16);